scroll-reth

diff: ignored:
+26214
-476
+192
-53

This is an overview of the changes in scroll-reth, a fork of reth.

diff --git reth/crates/scroll/alloy/consensus/Cargo.toml scroll-reth/crates/scroll/alloy/consensus/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..68b529906d078f3ea5e1ad11178cc53c3eb5cb31 --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/Cargo.toml @@ -0,0 +1,103 @@ +[package] +name = "scroll-alloy-consensus" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# alloy +alloy-rlp.workspace = true +alloy-eips.workspace = true +alloy-consensus.workspace = true +alloy-primitives = { workspace = true, features = ["rlp"] } + +# misc +derive_more = { workspace = true, features = ["display"] } +serde_with = { workspace = true, optional = true } + +# arbitrary +arbitrary = { workspace = true, features = ["derive"], optional = true } + +reth-codecs = { workspace = true, optional = true } +reth-codecs-derive = { workspace = true, optional = true } + +# required by reth-codecs +modular-bitfield = { workspace = true, optional = true } + +# serde +alloy-serde = { workspace = true, optional = true } +serde = { workspace = true, features = ["derive"], optional = true } + +[dev-dependencies] +rand.workspace = true +bincode.workspace = true +serde_json.workspace = true +arbitrary = { workspace = true, features = ["derive"] } +alloy-primitives = { workspace = true, features = ["rand", "arbitrary"] } +reth-codecs = { workspace = true, features = ["test-utils"] } +proptest-arbitrary-interop.workspace = true +proptest.workspace = true +test-fuzz.workspace = true + +[features] +default = ["std"] +std = [ + "serde/std", + "alloy-primitives/std", + "reth-codecs?/std", + "alloy-consensus/std", + "alloy-eips/std", + "alloy-rlp/std", + "alloy-serde/std", + "proptest/std", + "rand/std", + "derive_more/std", + "serde_json/std", + "serde_with?/std", +] +k256 = [ + "alloy-primitives/k256", + "alloy-consensus/k256", +] +kzg = [ + "alloy-eips/kzg", + "alloy-consensus/kzg", + "std", +] +reth-codec = [ + "dep:reth-codecs", + "dep:reth-codecs-derive", + "modular-bitfield", + "std", +] +arbitrary = [ + "std", + "dep:arbitrary", + "alloy-primitives/arbitrary", + "alloy-consensus/arbitrary", + "alloy-eips/arbitrary", + "alloy-serde/arbitrary", + "reth-codecs?/arbitrary", + "alloy-primitives/rand", +] +serde = [ + "dep:serde", + "dep:alloy-serde", + "alloy-primitives/serde", + "alloy-consensus/serde", + "alloy-eips/serde", + "rand/serde", + "reth-codecs?/serde", +] +serde-bincode-compat = [ + "dep:serde_with", + "alloy-consensus/serde-bincode-compat", + "alloy-eips/serde-bincode-compat", +]
diff --git reth/crates/scroll/alloy/consensus/README.md scroll-reth/crates/scroll/alloy/consensus/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b311fb2629b4e1adfe3066efc064fd386dc8e1f5 --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/README.md @@ -0,0 +1,15 @@ +# scroll-alloy-consensus + +Scroll consensus interface. + +This crate contains constants, types, and functions for implementing Scroll EL consensus and communication. This +includes an extended `ScrollTxEnvelope` type with l1 messages. + +In general a type belongs in this crate if it exists in the `alloy-consensus` crate, but was modified from the base Ethereum protocol in Scroll. +For consensus types that are not modified by Scroll, the `alloy-consensus` types should be used instead. + +## Provenance + +Much of this code was ported from [reth-primitives] as part of ongoing alloy migrations. + +[reth-primitives]: https://github.com/paradigmxyz/reth/tree/main/crates/primitives
diff --git reth/crates/scroll/alloy/consensus/src/lib.rs scroll-reth/crates/scroll/alloy/consensus/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..061a6d6969fb74ceaef66e2e0ddb3d6a8f4065a3 --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/lib.rs @@ -0,0 +1,30 @@ +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/alloy.jpg", + html_favicon_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/favicon.ico" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +extern crate alloc as std; + +mod transaction; +pub use transaction::{ + ScrollAdditionalInfo, ScrollL1MessageTransactionFields, ScrollPooledTransaction, + ScrollTransaction, ScrollTransactionInfo, ScrollTxEnvelope, ScrollTxType, + ScrollTypedTransaction, TxL1Message, L1_MESSAGE_TRANSACTION_TYPE, L1_MESSAGE_TX_TYPE_ID, +}; + +mod receipt; +pub use receipt::{ScrollReceiptEnvelope, ScrollReceiptWithBloom, ScrollTransactionReceipt}; + +#[cfg(feature = "serde")] +pub use transaction::serde_l1_message_tx_rpc; + +/// Bincode-compatible serde implementations. +#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))] +pub mod serde_bincode_compat { + pub use super::transaction::serde_bincode_compat::*; +}
diff --git reth/crates/scroll/alloy/consensus/src/receipt/envelope.rs scroll-reth/crates/scroll/alloy/consensus/src/receipt/envelope.rs new file mode 100644 index 0000000000000000000000000000000000000000..2ba0afdd84d9962c5ae4325ac7c684010a869a6a --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/receipt/envelope.rs @@ -0,0 +1,373 @@ +//! Receipt envelope types for Scroll. + +use crate::ScrollTxType; +use std::vec::Vec; + +use alloy_consensus::{Eip658Value, Receipt, ReceiptWithBloom, TxReceipt}; +use alloy_eips::{ + eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718}, + Typed2718, +}; +use alloy_primitives::{logs_bloom, Bloom, Log}; +use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable}; + +/// Receipt envelope, as defined in [EIP-2718], modified for Scroll chains. +/// +/// This enum distinguishes between tagged and untagged legacy receipts, as the +/// in-protocol merkle tree may commit to EITHER 0-prefixed or raw. Therefore +/// we must ensure that encoding returns the precise byte-array that was +/// decoded, preserving the presence or absence of the `TransactionType` flag. +/// +/// Transaction receipt payloads are specified in their respective EIPs. +/// +/// [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(tag = "type"))] +#[non_exhaustive] +pub enum ScrollReceiptEnvelope<T = Log> { + /// Receipt envelope with no type flag. + #[cfg_attr(feature = "serde", serde(rename = "0x0", alias = "0x00"))] + Legacy(ReceiptWithBloom<Receipt<T>>), + /// Receipt envelope with type flag 1, containing a [EIP-2930] receipt. + /// + /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + #[cfg_attr(feature = "serde", serde(rename = "0x1", alias = "0x01"))] + Eip2930(ReceiptWithBloom<Receipt<T>>), + /// Receipt envelope with type flag 2, containing a [EIP-1559] receipt. + /// + /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 + #[cfg_attr(feature = "serde", serde(rename = "0x2", alias = "0x02"))] + Eip1559(ReceiptWithBloom<Receipt<T>>), + /// Receipt envelope with type flag 4, containing a [EIP-7702] receipt. + /// + /// [EIP-7702]: https://eips.ethereum.org/EIPS/eip-7702 + #[cfg_attr(feature = "serde", serde(rename = "0x4", alias = "0x04"))] + Eip7702(ReceiptWithBloom<Receipt<T>>), + /// Receipt envelope with type flag 126, containing a [Scroll-L1-Message] receipt. + #[cfg_attr(feature = "serde", serde(rename = "0x7e", alias = "0x7E"))] + L1Message(ReceiptWithBloom<Receipt<T>>), +} + +impl ScrollReceiptEnvelope<Log> { + /// Creates a new [`ScrollReceiptEnvelope`] from the given parts. + pub fn from_parts<'a>( + status: bool, + cumulative_gas_used: u64, + logs: impl IntoIterator<Item = &'a Log>, + tx_type: ScrollTxType, + ) -> Self { + let logs = logs.into_iter().cloned().collect::<Vec<_>>(); + let logs_bloom = logs_bloom(&logs); + let inner_receipt = + Receipt { status: Eip658Value::Eip658(status), cumulative_gas_used, logs }; + match tx_type { + ScrollTxType::Legacy => { + Self::Legacy(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) + } + ScrollTxType::Eip2930 => { + Self::Eip2930(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) + } + ScrollTxType::Eip1559 => { + Self::Eip1559(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) + } + ScrollTxType::Eip7702 => { + Self::Eip7702(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) + } + ScrollTxType::L1Message => { + Self::L1Message(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) + } + } + } +} + +impl<T> ScrollReceiptEnvelope<T> { + /// Return the [`ScrollTxType`] of the inner receipt. + pub const fn tx_type(&self) -> ScrollTxType { + match self { + Self::Legacy(_) => ScrollTxType::Legacy, + Self::Eip2930(_) => ScrollTxType::Eip2930, + Self::Eip1559(_) => ScrollTxType::Eip1559, + Self::Eip7702(_) => ScrollTxType::Eip7702, + Self::L1Message(_) => ScrollTxType::L1Message, + } + } + + /// Return true if the transaction was successful. + pub const fn is_success(&self) -> bool { + self.status() + } + + /// Returns the success status of the receipt's transaction. + pub const fn status(&self) -> bool { + self.as_receipt().unwrap().status.coerce_status() + } + + /// Returns the cumulative gas used at this receipt. + pub const fn cumulative_gas_used(&self) -> u64 { + self.as_receipt().unwrap().cumulative_gas_used + } + + /// Return the receipt logs. + pub fn logs(&self) -> &[T] { + &self.as_receipt().unwrap().logs + } + + /// Return the receipt's bloom. + pub const fn logs_bloom(&self) -> &Bloom { + match self { + Self::Legacy(t) | + Self::Eip2930(t) | + Self::Eip1559(t) | + Self::Eip7702(t) | + Self::L1Message(t) => &t.logs_bloom, + } + } + + /// Returns the L1 message receipt if it is a deposit receipt. + pub const fn as_l1_message_receipt_with_bloom(&self) -> Option<&ReceiptWithBloom<Receipt<T>>> { + match self { + Self::L1Message(t) => Some(t), + _ => None, + } + } + + /// Returns the L1 message receipt if it is a deposit receipt. + pub const fn as_l1_message_receipt(&self) -> Option<&Receipt<T>> { + match self { + Self::L1Message(t) => Some(&t.receipt), + _ => None, + } + } + + /// Return the inner receipt. Currently this is infallible, however, future + /// receipt types may be added. + pub const fn as_receipt(&self) -> Option<&Receipt<T>> { + match self { + Self::Legacy(t) | + Self::Eip2930(t) | + Self::Eip1559(t) | + Self::Eip7702(t) | + Self::L1Message(t) => Some(&t.receipt), + } + } +} + +impl ScrollReceiptEnvelope { + /// Get the length of the inner receipt in the 2718 encoding. + pub fn inner_length(&self) -> usize { + match self { + Self::Legacy(t) | + Self::Eip2930(t) | + Self::Eip1559(t) | + Self::Eip7702(t) | + Self::L1Message(t) => t.length(), + } + } + + /// Calculate the length of the rlp payload of the network encoded receipt. + pub fn rlp_payload_length(&self) -> usize { + let length = self.inner_length(); + match self { + Self::Legacy(_) => length, + _ => length + 1, + } + } +} + +impl<T> TxReceipt for ScrollReceiptEnvelope<T> +where + T: Clone + core::fmt::Debug + PartialEq + Eq + Send + Sync, +{ + type Log = T; + + fn status_or_post_state(&self) -> Eip658Value { + self.as_receipt().unwrap().status + } + + fn status(&self) -> bool { + self.as_receipt().unwrap().status.coerce_status() + } + + /// Return the receipt's bloom. + fn bloom(&self) -> Bloom { + *self.logs_bloom() + } + + fn bloom_cheap(&self) -> Option<Bloom> { + Some(self.bloom()) + } + + /// Returns the cumulative gas used at this receipt. + fn cumulative_gas_used(&self) -> u64 { + self.as_receipt().unwrap().cumulative_gas_used + } + + /// Return the receipt logs. + fn logs(&self) -> &[T] { + &self.as_receipt().unwrap().logs + } +} + +impl Encodable for ScrollReceiptEnvelope { + fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { + self.network_encode(out) + } + + fn length(&self) -> usize { + let mut payload_length = self.rlp_payload_length(); + if !self.is_legacy() { + payload_length += length_of_length(payload_length); + } + payload_length + } +} + +impl Decodable for ScrollReceiptEnvelope { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> { + Self::network_decode(buf) + .map_or_else(|_| Err(alloy_rlp::Error::Custom("Unexpected type")), Ok) + } +} + +impl Encodable2718 for ScrollReceiptEnvelope { + fn type_flag(&self) -> Option<u8> { + match self { + Self::Legacy(_) => None, + Self::Eip2930(_) => Some(ScrollTxType::Eip2930 as u8), + Self::Eip1559(_) => Some(ScrollTxType::Eip1559 as u8), + Self::Eip7702(_) => Some(ScrollTxType::Eip7702 as u8), + Self::L1Message(_) => Some(ScrollTxType::L1Message as u8), + } + } + + fn encode_2718_len(&self) -> usize { + self.inner_length() + !self.is_legacy() as usize + } + + fn encode_2718(&self, out: &mut dyn BufMut) { + match self.type_flag() { + None => {} + Some(ty) => out.put_u8(ty), + } + match self { + Self::Legacy(t) | + Self::Eip2930(t) | + Self::Eip1559(t) | + Self::Eip7702(t) | + Self::L1Message(t) => t.encode(out), + } + } +} + +impl Typed2718 for ScrollReceiptEnvelope { + fn ty(&self) -> u8 { + let ty = match self { + Self::Legacy(_) => ScrollTxType::Legacy, + Self::Eip2930(_) => ScrollTxType::Eip2930, + Self::Eip1559(_) => ScrollTxType::Eip1559, + Self::Eip7702(_) => ScrollTxType::Eip7702, + Self::L1Message(_) => ScrollTxType::L1Message, + }; + ty as u8 + } +} + +impl Decodable2718 for ScrollReceiptEnvelope { + fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> { + match ty.try_into().map_err(|_| Eip2718Error::UnexpectedType(ty))? { + ScrollTxType::Legacy => { + Err(alloy_rlp::Error::Custom("type-0 eip2718 transactions are not supported") + .into()) + } + ScrollTxType::Eip2930 => Ok(Self::Eip2930(Decodable::decode(buf)?)), + ScrollTxType::Eip1559 => Ok(Self::Eip1559(Decodable::decode(buf)?)), + ScrollTxType::Eip7702 => Ok(Self::Eip7702(Decodable::decode(buf)?)), + ScrollTxType::L1Message => Ok(Self::L1Message(Decodable::decode(buf)?)), + } + } + + fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> { + Ok(Self::Legacy(Decodable::decode(buf)?)) + } +} + +#[cfg(all(test, feature = "arbitrary"))] +impl<'a, T> arbitrary::Arbitrary<'a> for ScrollReceiptEnvelope<T> +where + T: arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> { + match u.int_in_range(0..=4)? { + 0 => Ok(Self::Legacy(ReceiptWithBloom::arbitrary(u)?)), + 1 => Ok(Self::Eip2930(ReceiptWithBloom::arbitrary(u)?)), + 2 => Ok(Self::Eip1559(ReceiptWithBloom::arbitrary(u)?)), + _ => Ok(Self::L1Message(ReceiptWithBloom::arbitrary(u)?)), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_consensus::{Receipt, ReceiptWithBloom}; + use alloy_eips::eip2718::Encodable2718; + use alloy_primitives::{address, b256, bytes, hex, Log, LogData}; + use alloy_rlp::Encodable; + + #[cfg(not(feature = "std"))] + use alloc::vec; + + // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 + #[test] + fn encode_legacy_receipt() { + let expected = hex!("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"); + + let mut data = vec![]; + let receipt = ScrollReceiptEnvelope::Legacy(ReceiptWithBloom { + receipt: Receipt { + status: false.into(), + cumulative_gas_used: 0x1, + logs: vec![Log { + address: address!("0000000000000000000000000000000000000011"), + data: LogData::new_unchecked( + vec![ + b256!( + "000000000000000000000000000000000000000000000000000000000000dead" + ), + b256!( + "000000000000000000000000000000000000000000000000000000000000beef" + ), + ], + bytes!("0100ff"), + ), + }], + }, + logs_bloom: [0; 256].into(), + }); + + receipt.network_encode(&mut data); + + // check that the rlp length equals the length of the expected rlp + assert_eq!(receipt.length(), expected.len()); + assert_eq!(data, expected); + } + + #[test] + fn legacy_receipt_from_parts() { + let receipt = ScrollReceiptEnvelope::from_parts(true, 100, vec![], ScrollTxType::Legacy); + assert!(receipt.status()); + assert_eq!(receipt.cumulative_gas_used(), 100); + assert_eq!(receipt.logs().len(), 0); + assert_eq!(receipt.tx_type(), ScrollTxType::Legacy); + } + + #[test] + fn l1_message_receipt_from_parts() { + let receipt = ScrollReceiptEnvelope::from_parts(true, 100, vec![], ScrollTxType::L1Message); + assert!(receipt.status()); + assert_eq!(receipt.cumulative_gas_used(), 100); + assert_eq!(receipt.logs().len(), 0); + assert_eq!(receipt.tx_type(), ScrollTxType::L1Message); + } +}
diff --git reth/crates/scroll/alloy/consensus/src/receipt/mod.rs scroll-reth/crates/scroll/alloy/consensus/src/receipt/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..c06b4838314b7c82fa98a4e23231d6c0b00efc98 --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/receipt/mod.rs @@ -0,0 +1,6 @@ +mod envelope; +pub use envelope::ScrollReceiptEnvelope; + +#[allow(clippy::module_inception)] +mod receipt; +pub use receipt::{ScrollReceiptWithBloom, ScrollTransactionReceipt};
diff --git reth/crates/scroll/alloy/consensus/src/receipt/receipt.rs scroll-reth/crates/scroll/alloy/consensus/src/receipt/receipt.rs new file mode 100644 index 0000000000000000000000000000000000000000..5cdd1b3e1134d5a0d3dc6f9e6888eb7d2a441814 --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/receipt/receipt.rs @@ -0,0 +1,256 @@ +//! Transaction receipt types for Scroll. + +use alloy_consensus::{ + Eip658Value, Receipt, ReceiptWithBloom, RlpDecodableReceipt, RlpEncodableReceipt, TxReceipt, +}; +use alloy_primitives::{Bloom, Log, U256}; +use alloy_rlp::{Buf, BufMut, Decodable, Encodable, Header}; + +/// Receipt containing result of transaction execution. +#[derive(Clone, Debug, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct ScrollTransactionReceipt<T = Log> { + /// The inner receipt type. + #[cfg_attr(feature = "serde", serde(flatten))] + pub inner: Receipt<T>, + /// L1 fee for Scroll transactions. + pub l1_fee: U256, +} + +impl<T> ScrollTransactionReceipt<T> { + /// Returns a new [`ScrollTransactionReceipt`] from the inner receipt and the l1 fee. + pub const fn new(inner: Receipt<T>, l1_fee: U256) -> Self { + Self { inner, l1_fee } + } +} + +impl ScrollTransactionReceipt { + /// Calculates [`Log`]'s bloom filter. This is slow operation and [`ScrollReceiptWithBloom`] + /// can be used to cache this value. + pub fn bloom_slow(&self) -> Bloom { + self.inner.logs.iter().collect() + } + + /// Calculates the bloom filter for the receipt and returns the [`ScrollReceiptWithBloom`] + /// container type. + pub fn with_bloom(self) -> ScrollReceiptWithBloom { + self.into() + } +} + +impl<T: Encodable> ScrollTransactionReceipt<T> { + /// Returns length of RLP-encoded receipt fields with the given [`Bloom`] without an RLP header. + /// Does not include the L1 fee field which is not part of the consensus encoding of a receipt. + /// <https://github.com/scroll-tech/go-ethereum/blob/9fff27e4f34fb5097100ed76ee725ce056267f4b/core/types/receipt.go#L96-L102> + pub fn rlp_encoded_fields_length_with_bloom(&self, bloom: &Bloom) -> usize { + self.inner.rlp_encoded_fields_length_with_bloom(bloom) + } + + /// RLP-encodes receipt fields with the given [`Bloom`] without an RLP header. + /// Does not include the L1 fee field which is not part of the consensus encoding of a receipt. + /// <https://github.com/scroll-tech/go-ethereum/blob/9fff27e4f34fb5097100ed76ee725ce056267f4b/core/types/receipt.go#L96-L102> + pub fn rlp_encode_fields_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) { + self.inner.rlp_encode_fields_with_bloom(bloom, out); + } + + /// Returns RLP header for this receipt encoding with the given [`Bloom`]. + /// Does not include the L1 fee field which is not part of the consensus encoding of a receipt. + /// <https://github.com/scroll-tech/go-ethereum/blob/9fff27e4f34fb5097100ed76ee725ce056267f4b/core/types/receipt.go#L96-L102> + pub fn rlp_header_with_bloom(&self, bloom: &Bloom) -> Header { + Header { list: true, payload_length: self.rlp_encoded_fields_length_with_bloom(bloom) } + } +} + +impl<T: Decodable> ScrollTransactionReceipt<T> { + /// RLP-decodes receipt's field with a [`Bloom`]. + /// + /// Does not expect an RLP header. + pub fn rlp_decode_fields_with_bloom( + buf: &mut &[u8], + ) -> alloy_rlp::Result<ReceiptWithBloom<Self>> { + let ReceiptWithBloom { receipt: inner, logs_bloom } = + Receipt::rlp_decode_fields_with_bloom(buf)?; + + Ok(ReceiptWithBloom { logs_bloom, receipt: Self { inner, l1_fee: Default::default() } }) + } +} + +impl<T> AsRef<Receipt<T>> for ScrollTransactionReceipt<T> { + fn as_ref(&self) -> &Receipt<T> { + &self.inner + } +} + +impl<T> TxReceipt for ScrollTransactionReceipt<T> +where + T: AsRef<Log> + Clone + core::fmt::Debug + PartialEq + Eq + Send + Sync, +{ + type Log = T; + + fn status_or_post_state(&self) -> Eip658Value { + self.inner.status_or_post_state() + } + + fn status(&self) -> bool { + self.inner.status() + } + + fn bloom(&self) -> Bloom { + self.inner.bloom_slow() + } + + fn cumulative_gas_used(&self) -> u64 { + self.inner.cumulative_gas_used() + } + + fn logs(&self) -> &[Self::Log] { + self.inner.logs() + } +} + +impl<T: Encodable> RlpEncodableReceipt for ScrollTransactionReceipt<T> { + fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize { + self.rlp_header_with_bloom(bloom).length_with_payload() + } + + fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) { + self.rlp_header_with_bloom(bloom).encode(out); + self.rlp_encode_fields_with_bloom(bloom, out); + } +} + +impl<T: Decodable> RlpDecodableReceipt for ScrollTransactionReceipt<T> { + fn rlp_decode_with_bloom(buf: &mut &[u8]) -> alloy_rlp::Result<ReceiptWithBloom<Self>> { + let header = Header::decode(buf)?; + if !header.list { + return Err(alloy_rlp::Error::UnexpectedString); + } + + if buf.len() < header.payload_length { + return Err(alloy_rlp::Error::InputTooShort); + } + + // Note: we pass a separate buffer to `rlp_decode_fields_with_bloom` to allow it decode + // optional fields based on the remaining length. + let mut fields_buf = &buf[..header.payload_length]; + let this = Self::rlp_decode_fields_with_bloom(&mut fields_buf)?; + + if !fields_buf.is_empty() { + return Err(alloy_rlp::Error::UnexpectedLength); + } + + buf.advance(header.payload_length); + + Ok(this) + } +} + +/// [`ScrollTransactionReceipt`] with calculated bloom filter, modified for Scroll. +/// +/// This convenience type allows us to lazily calculate the bloom filter for a +/// receipt, similar to [`Sealed`]. +/// +/// [`Sealed`]: alloy_consensus::Sealed +pub type ScrollReceiptWithBloom<T = Log> = ReceiptWithBloom<ScrollTransactionReceipt<T>>; + +#[cfg(any(test, feature = "arbitrary"))] +impl<'a, T> arbitrary::Arbitrary<'a> for ScrollTransactionReceipt<T> +where + T: arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> { + #[cfg(not(feature = "std"))] + use alloc::vec::Vec; + Ok(Self { + inner: Receipt { + status: Eip658Value::arbitrary(u)?, + cumulative_gas_used: u64::arbitrary(u)?, + logs: Vec::<T>::arbitrary(u)?, + }, + l1_fee: U256::arbitrary(u)?, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_consensus::Receipt; + use alloy_primitives::{address, b256, bytes, hex, Bytes, Log, LogData}; + use alloy_rlp::{Decodable, Encodable}; + + #[cfg(not(feature = "std"))] + use alloc::{vec, vec::Vec}; + + // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 + #[test] + fn decode_legacy_receipt() { + let data = hex!("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"); + + // EIP658Receipt + let expected = + ScrollReceiptWithBloom { + receipt: ScrollTransactionReceipt { + inner: Receipt { + status: false.into(), + cumulative_gas_used: 0x1, + logs: vec![Log { + address: address!("0000000000000000000000000000000000000011"), + data: LogData::new_unchecked( + vec![ + b256!("000000000000000000000000000000000000000000000000000000000000dead"), + b256!("000000000000000000000000000000000000000000000000000000000000beef"), + ], + bytes!("0100ff"), + ), + }], + }, + l1_fee: U256::ZERO + }, + logs_bloom: [0; 256].into(), + }; + + let receipt = ScrollReceiptWithBloom::decode(&mut &data[..]).unwrap(); + assert_eq!(receipt, expected); + } + + #[test] + fn gigantic_receipt() { + let receipt = ScrollTransactionReceipt { + inner: Receipt { + cumulative_gas_used: 16747627, + status: true.into(), + logs: vec![ + Log { + address: address!("4bf56695415f725e43c3e04354b604bcfb6dfb6e"), + data: LogData::new_unchecked( + vec![b256!( + "c69dc3d7ebff79e41f525be431d5cd3cc08f80eaf0f7819054a726eeb7086eb9" + )], + Bytes::from(vec![1; 0xffffff]), + ), + }, + Log { + address: address!("faca325c86bf9c2d5b413cd7b90b209be92229c2"), + data: LogData::new_unchecked( + vec![b256!( + "8cca58667b1e9ffa004720ac99a3d61a138181963b294d270d91c53d36402ae2" + )], + Bytes::from(vec![1; 0xffffff]), + ), + }, + ], + }, + l1_fee: U256::ZERO, + } + .with_bloom(); + + let mut data = vec![]; + + receipt.encode(&mut data); + let decoded = ScrollReceiptWithBloom::decode(&mut &data[..]).unwrap(); + + assert_eq!(decoded, receipt); + } +}
diff --git reth/crates/scroll/alloy/consensus/src/transaction/envelope.rs scroll-reth/crates/scroll/alloy/consensus/src/transaction/envelope.rs new file mode 100644 index 0000000000000000000000000000000000000000..2905667012fb6a18c9134e4beaf459fd3b4534bb --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/transaction/envelope.rs @@ -0,0 +1,960 @@ +use crate::{ScrollPooledTransaction, ScrollTxType, ScrollTypedTransaction, TxL1Message}; +use core::hash::Hash; + +use alloy_consensus::{ + error::ValueError, transaction::RlpEcdsaDecodableTx, Sealable, Sealed, Signed, Transaction, + TxEip1559, TxEip2930, TxEip7702, TxLegacy, Typed2718, +}; +use alloy_eips::{ + eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718}, + eip2930::AccessList, + eip7702::SignedAuthorization, +}; +use alloy_primitives::{Address, Bytes, Signature, TxKind, B256, U256}; +use alloy_rlp::{Decodable, Encodable}; +#[cfg(feature = "reth-codec")] +use reth_codecs::{ + Compact, + __private::bytes::BufMut, + alloy::transaction::{CompactEnvelope, Envelope, FromTxCompact, ToTxCompact}, +}; + +/// The Ethereum [EIP-2718] Transaction Envelope, modified for Scroll chains. +/// +/// # Note: +/// +/// This enum distinguishes between tagged and untagged legacy transactions, as +/// the in-protocol merkle tree may commit to EITHER 0-prefixed or raw. +/// Therefore we must ensure that encoding returns the precise byte-array that +/// was decoded, preserving the presence or absence of the `TransactionType` +/// flag. +/// +/// [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + serde(into = "serde_from::TaggedTxEnvelope", from = "serde_from::MaybeTaggedTxEnvelope") +)] +#[cfg_attr(all(any(test, feature = "arbitrary"), feature = "k256"), derive(arbitrary::Arbitrary))] +pub enum ScrollTxEnvelope { + /// An untagged [`TxLegacy`]. + Legacy(Signed<TxLegacy>), + /// A [`TxEip2930`] tagged with type 1. + Eip2930(Signed<TxEip2930>), + /// A [`TxEip1559`] tagged with type 2. + Eip1559(Signed<TxEip1559>), + /// EIP-7702 transaction + Eip7702(Signed<TxEip7702>), + /// A [`TxL1Message`] tagged with type 0x7E. + L1Message(Sealed<TxL1Message>), +} + +impl From<Signed<TxLegacy>> for ScrollTxEnvelope { + fn from(v: Signed<TxLegacy>) -> Self { + Self::Legacy(v) + } +} + +impl From<Signed<TxEip2930>> for ScrollTxEnvelope { + fn from(v: Signed<TxEip2930>) -> Self { + Self::Eip2930(v) + } +} + +impl From<Signed<TxEip1559>> for ScrollTxEnvelope { + fn from(v: Signed<TxEip1559>) -> Self { + Self::Eip1559(v) + } +} + +impl From<Signed<TxEip7702>> for ScrollTxEnvelope { + fn from(v: Signed<TxEip7702>) -> Self { + Self::Eip7702(v) + } +} + +impl From<TxL1Message> for ScrollTxEnvelope { + fn from(v: TxL1Message) -> Self { + v.seal_slow().into() + } +} + +impl From<Sealed<TxL1Message>> for ScrollTxEnvelope { + fn from(v: Sealed<TxL1Message>) -> Self { + Self::L1Message(v) + } +} + +impl From<Signed<ScrollTypedTransaction>> for ScrollTxEnvelope { + fn from(value: Signed<ScrollTypedTransaction>) -> Self { + let (tx, sig, hash) = value.into_parts(); + match tx { + ScrollTypedTransaction::Legacy(tx_legacy) => { + let tx = Signed::new_unchecked(tx_legacy, sig, hash); + Self::Legacy(tx) + } + ScrollTypedTransaction::Eip2930(tx_eip2930) => { + let tx = Signed::new_unchecked(tx_eip2930, sig, hash); + Self::Eip2930(tx) + } + ScrollTypedTransaction::Eip1559(tx_eip1559) => { + let tx = Signed::new_unchecked(tx_eip1559, sig, hash); + Self::Eip1559(tx) + } + ScrollTypedTransaction::Eip7702(tx_eip7702) => { + let tx = Signed::new_unchecked(tx_eip7702, sig, hash); + Self::Eip7702(tx) + } + ScrollTypedTransaction::L1Message(tx) => { + Self::L1Message(Sealed::new_unchecked(tx, hash)) + } + } + } +} + +impl Typed2718 for ScrollTxEnvelope { + fn ty(&self) -> u8 { + match self { + Self::Legacy(tx) => tx.tx().ty(), + Self::Eip2930(tx) => tx.tx().ty(), + Self::Eip1559(tx) => tx.tx().ty(), + Self::Eip7702(tx) => tx.tx().ty(), + Self::L1Message(tx) => tx.ty(), + } + } +} + +impl Transaction for ScrollTxEnvelope { + fn chain_id(&self) -> Option<u64> { + match self { + Self::Legacy(tx) => tx.tx().chain_id(), + Self::Eip2930(tx) => tx.tx().chain_id(), + Self::Eip1559(tx) => tx.tx().chain_id(), + Self::Eip7702(tx) => tx.tx().chain_id(), + Self::L1Message(tx) => tx.chain_id(), + } + } + + fn nonce(&self) -> u64 { + match self { + Self::Legacy(tx) => tx.tx().nonce(), + Self::Eip2930(tx) => tx.tx().nonce(), + Self::Eip1559(tx) => tx.tx().nonce(), + Self::Eip7702(tx) => tx.tx().nonce(), + Self::L1Message(tx) => tx.nonce(), + } + } + + fn gas_limit(&self) -> u64 { + match self { + Self::Legacy(tx) => tx.tx().gas_limit(), + Self::Eip2930(tx) => tx.tx().gas_limit(), + Self::Eip1559(tx) => tx.tx().gas_limit(), + Self::Eip7702(tx) => tx.tx().gas_limit(), + Self::L1Message(tx) => tx.gas_limit(), + } + } + + fn gas_price(&self) -> Option<u128> { + match self { + Self::Legacy(tx) => tx.tx().gas_price(), + Self::Eip2930(tx) => tx.tx().gas_price(), + Self::Eip1559(tx) => tx.tx().gas_price(), + Self::Eip7702(tx) => tx.tx().gas_price(), + Self::L1Message(tx) => tx.gas_price(), + } + } + + fn max_fee_per_gas(&self) -> u128 { + match self { + Self::Legacy(tx) => tx.tx().max_fee_per_gas(), + Self::Eip2930(tx) => tx.tx().max_fee_per_gas(), + Self::Eip1559(tx) => tx.tx().max_fee_per_gas(), + Self::Eip7702(tx) => tx.tx().max_fee_per_gas(), + Self::L1Message(tx) => tx.max_fee_per_gas(), + } + } + + fn max_priority_fee_per_gas(&self) -> Option<u128> { + match self { + Self::Legacy(tx) => tx.tx().max_priority_fee_per_gas(), + Self::Eip2930(tx) => tx.tx().max_priority_fee_per_gas(), + Self::Eip1559(tx) => tx.tx().max_priority_fee_per_gas(), + Self::Eip7702(tx) => tx.tx().max_priority_fee_per_gas(), + Self::L1Message(tx) => tx.max_priority_fee_per_gas(), + } + } + + fn max_fee_per_blob_gas(&self) -> Option<u128> { + match self { + Self::Legacy(tx) => tx.tx().max_fee_per_blob_gas(), + Self::Eip2930(tx) => tx.tx().max_fee_per_blob_gas(), + Self::Eip1559(tx) => tx.tx().max_fee_per_blob_gas(), + Self::Eip7702(tx) => tx.tx().max_fee_per_blob_gas(), + Self::L1Message(tx) => tx.max_fee_per_blob_gas(), + } + } + + fn priority_fee_or_price(&self) -> u128 { + match self { + Self::Legacy(tx) => tx.tx().priority_fee_or_price(), + Self::Eip2930(tx) => tx.tx().priority_fee_or_price(), + Self::Eip1559(tx) => tx.tx().priority_fee_or_price(), + Self::Eip7702(tx) => tx.tx().priority_fee_or_price(), + Self::L1Message(tx) => tx.priority_fee_or_price(), + } + } + + fn to(&self) -> Option<Address> { + match self { + Self::Legacy(tx) => tx.tx().to(), + Self::Eip2930(tx) => tx.tx().to(), + Self::Eip1559(tx) => tx.tx().to(), + Self::Eip7702(tx) => tx.tx().to(), + Self::L1Message(tx) => tx.to(), + } + } + + fn kind(&self) -> TxKind { + match self { + Self::Legacy(tx) => tx.tx().kind(), + Self::Eip2930(tx) => tx.tx().kind(), + Self::Eip1559(tx) => tx.tx().kind(), + Self::Eip7702(tx) => tx.tx().kind(), + Self::L1Message(tx) => tx.kind(), + } + } + + fn value(&self) -> U256 { + match self { + Self::Legacy(tx) => tx.tx().value(), + Self::Eip2930(tx) => tx.tx().value(), + Self::Eip1559(tx) => tx.tx().value(), + Self::Eip7702(tx) => tx.tx().value(), + Self::L1Message(tx) => tx.value(), + } + } + + fn input(&self) -> &Bytes { + match self { + Self::Legacy(tx) => tx.tx().input(), + Self::Eip2930(tx) => tx.tx().input(), + Self::Eip1559(tx) => tx.tx().input(), + Self::Eip7702(tx) => tx.tx().input(), + Self::L1Message(tx) => tx.input(), + } + } + + fn access_list(&self) -> Option<&AccessList> { + match self { + Self::Legacy(tx) => tx.tx().access_list(), + Self::Eip2930(tx) => tx.tx().access_list(), + Self::Eip1559(tx) => tx.tx().access_list(), + Self::Eip7702(tx) => tx.tx().access_list(), + Self::L1Message(tx) => tx.access_list(), + } + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + match self { + Self::Legacy(tx) => tx.tx().blob_versioned_hashes(), + Self::Eip2930(tx) => tx.tx().blob_versioned_hashes(), + Self::Eip1559(tx) => tx.tx().blob_versioned_hashes(), + Self::Eip7702(tx) => tx.tx().blob_versioned_hashes(), + Self::L1Message(tx) => tx.blob_versioned_hashes(), + } + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + match self { + Self::Legacy(tx) => tx.tx().authorization_list(), + Self::Eip2930(tx) => tx.tx().authorization_list(), + Self::Eip1559(tx) => tx.tx().authorization_list(), + Self::Eip7702(tx) => tx.tx().authorization_list(), + Self::L1Message(tx) => tx.authorization_list(), + } + } + + fn is_dynamic_fee(&self) -> bool { + match self { + Self::Legacy(tx) => tx.tx().is_dynamic_fee(), + Self::Eip2930(tx) => tx.tx().is_dynamic_fee(), + Self::Eip1559(tx) => tx.tx().is_dynamic_fee(), + Self::Eip7702(tx) => tx.tx().is_dynamic_fee(), + Self::L1Message(tx) => tx.is_dynamic_fee(), + } + } + + fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 { + match self { + Self::Legacy(tx) => tx.tx().effective_gas_price(base_fee), + Self::Eip2930(tx) => tx.tx().effective_gas_price(base_fee), + Self::Eip1559(tx) => tx.tx().effective_gas_price(base_fee), + Self::Eip7702(tx) => tx.tx().effective_gas_price(base_fee), + Self::L1Message(tx) => tx.effective_gas_price(base_fee), + } + } + + fn is_create(&self) -> bool { + match self { + Self::Legacy(tx) => tx.tx().is_create(), + Self::Eip2930(tx) => tx.tx().is_create(), + Self::Eip1559(tx) => tx.tx().is_create(), + Self::Eip7702(tx) => tx.tx().is_create(), + Self::L1Message(tx) => tx.is_create(), + } + } +} + +impl ScrollTxEnvelope { + /// Returns true if the transaction is a legacy transaction. + #[inline] + pub const fn is_legacy(&self) -> bool { + matches!(self, Self::Legacy(_)) + } + + /// Returns true if the transaction is an EIP-2930 transaction. + #[inline] + pub const fn is_eip2930(&self) -> bool { + matches!(self, Self::Eip2930(_)) + } + + /// Returns true if the transaction is an EIP-1559 transaction. + #[inline] + pub const fn is_eip1559(&self) -> bool { + matches!(self, Self::Eip1559(_)) + } + + /// Returns true if the transaction is an EIP-7702 transaction. + #[inline] + pub const fn is_eip7702(&self) -> bool { + matches!(self, Self::Eip7702(_)) + } + + /// Returns true if the transaction is a deposit transaction. + #[inline] + pub const fn is_l1_message(&self) -> bool { + matches!(self, Self::L1Message(_)) + } + + /// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction. + pub const fn as_legacy(&self) -> Option<&Signed<TxLegacy>> { + match self { + Self::Legacy(tx) => Some(tx), + _ => None, + } + } + + /// Returns the [`TxEip2930`] variant if the transaction is an EIP-2930 transaction. + pub const fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> { + match self { + Self::Eip2930(tx) => Some(tx), + _ => None, + } + } + + /// Returns the [`TxEip1559`] variant if the transaction is an EIP-1559 transaction. + pub const fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> { + match self { + Self::Eip1559(tx) => Some(tx), + _ => None, + } + } + + /// Returns the [`TxEip7702`] variant if the transaction is an EIP-1559 transaction. + pub const fn as_eip7702(&self) -> Option<&Signed<TxEip7702>> { + match self { + Self::Eip7702(tx) => Some(tx), + _ => None, + } + } + + /// Returns the [`TxL1Message`] variant if the transaction is a deposit transaction. + pub const fn as_l1_message(&self) -> Option<&Sealed<TxL1Message>> { + match self { + Self::L1Message(tx) => Some(tx), + _ => None, + } + } + + /// Return the [`ScrollTxType`] of the inner txn. + pub const fn tx_type(&self) -> ScrollTxType { + match self { + Self::Legacy(_) => ScrollTxType::Legacy, + Self::Eip2930(_) => ScrollTxType::Eip2930, + Self::Eip1559(_) => ScrollTxType::Eip1559, + Self::Eip7702(_) => ScrollTxType::Eip7702, + Self::L1Message(_) => ScrollTxType::L1Message, + } + } + + /// Return the length of the inner txn, including type byte length + pub fn eip2718_encoded_length(&self) -> usize { + match self { + Self::Legacy(t) => t.eip2718_encoded_length(), + Self::Eip2930(t) => t.eip2718_encoded_length(), + Self::Eip1559(t) => t.eip2718_encoded_length(), + Self::Eip7702(t) => t.eip2718_encoded_length(), + Self::L1Message(t) => t.eip2718_encoded_length(), + } + } + + /// Returns the signature for the transaction. + pub const fn signature(&self) -> Option<Signature> { + match self { + Self::Legacy(t) => Some(*t.signature()), + Self::Eip2930(t) => Some(*t.signature()), + Self::Eip1559(t) => Some(*t.signature()), + Self::Eip7702(t) => Some(*t.signature()), + Self::L1Message(_) => None, + } + } + + /// Converts the [`ScrollTxEnvelope`] into a [`ScrollPooledTransaction`], returns an error if + /// the transaction is a L1 message. + pub fn try_into_pooled(self) -> Result<ScrollPooledTransaction, ValueError<Self>> { + match self { + Self::Legacy(tx) => Ok(tx.into()), + Self::Eip2930(tx) => Ok(tx.into()), + Self::Eip1559(tx) => Ok(tx.into()), + Self::Eip7702(tx) => Ok(tx.into()), + Self::L1Message(tx) => Err(ValueError::new(tx.into(), "L1 messages cannot be pooled")), + } + } +} + +/// A Scroll chain transaction. +pub trait ScrollTransaction { + /// Returns true if the transaction is a L1 message. + fn is_l1_message(&self) -> bool; + /// Returns the queue index if the transaction is a L1 message, None otherwise. + fn queue_index(&self) -> Option<u64>; +} + +impl ScrollTransaction for ScrollTxEnvelope { + fn is_l1_message(&self) -> bool { + match self { + Self::Legacy(_) | Self::Eip2930(_) | Self::Eip1559(_) | Self::Eip7702(_) => false, + Self::L1Message(_) => true, + } + } + + fn queue_index(&self) -> Option<u64> { + match self { + Self::Legacy(_) | Self::Eip2930(_) | Self::Eip1559(_) | Self::Eip7702(_) => None, + Self::L1Message(tx) => Some(tx.queue_index), + } + } +} + +#[cfg(feature = "reth-codec")] +impl ToTxCompact for ScrollTxEnvelope { + fn to_tx_compact(&self, buf: &mut (impl BufMut + AsMut<[u8]>)) { + match self { + Self::Legacy(tx) => tx.tx().to_compact(buf), + Self::Eip2930(tx) => tx.tx().to_compact(buf), + Self::Eip1559(tx) => tx.tx().to_compact(buf), + Self::Eip7702(tx) => tx.tx().to_compact(buf), + Self::L1Message(tx) => tx.to_compact(buf), + }; + } +} + +#[cfg(feature = "reth-codec")] +impl FromTxCompact for ScrollTxEnvelope { + type TxType = ScrollTxType; + + fn from_tx_compact(buf: &[u8], tx_type: ScrollTxType, signature: Signature) -> (Self, &[u8]) { + match tx_type { + ScrollTxType::Legacy => { + let (tx, buf) = TxLegacy::from_compact(buf, buf.len()); + let tx = Signed::new_unhashed(tx, signature); + (Self::Legacy(tx), buf) + } + ScrollTxType::Eip2930 => { + let (tx, buf) = TxEip2930::from_compact(buf, buf.len()); + let tx = Signed::new_unhashed(tx, signature); + (Self::Eip2930(tx), buf) + } + ScrollTxType::Eip1559 => { + let (tx, buf) = TxEip1559::from_compact(buf, buf.len()); + let tx = Signed::new_unhashed(tx, signature); + (Self::Eip1559(tx), buf) + } + ScrollTxType::Eip7702 => { + let (tx, buf) = TxEip7702::from_compact(buf, buf.len()); + let tx = Signed::new_unhashed(tx, signature); + (Self::Eip7702(tx), buf) + } + ScrollTxType::L1Message => { + let (tx, buf) = TxL1Message::from_compact(buf, buf.len()); + let tx = Sealed::new(tx); + (Self::L1Message(tx), buf) + } + } + } +} + +#[cfg(feature = "reth-codec")] +const L1_MESSAGE_SIGNATURE: Signature = Signature::new(U256::ZERO, U256::ZERO, false); + +#[cfg(feature = "reth-codec")] +impl Envelope for ScrollTxEnvelope { + fn signature(&self) -> &Signature { + match self { + Self::Legacy(tx) => tx.signature(), + Self::Eip2930(tx) => tx.signature(), + Self::Eip1559(tx) => tx.signature(), + Self::Eip7702(tx) => tx.signature(), + Self::L1Message(_) => &L1_MESSAGE_SIGNATURE, + } + } + + fn tx_type(&self) -> Self::TxType { + Self::tx_type(self) + } +} + +#[cfg(feature = "reth-codec")] +impl Compact for ScrollTxEnvelope { + fn to_compact<B>(&self, buf: &mut B) -> usize + where + B: BufMut + AsMut<[u8]>, + { + CompactEnvelope::to_compact(self, buf) + } + + fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) { + CompactEnvelope::from_compact(buf, len) + } +} + +impl Encodable for ScrollTxEnvelope { + fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { + self.network_encode(out) + } + + fn length(&self) -> usize { + self.network_len() + } +} + +impl Decodable for ScrollTxEnvelope { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> { + Ok(Self::network_decode(buf)?) + } +} + +impl Decodable2718 for ScrollTxEnvelope { + fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> { + match ty.try_into().map_err(|_| Eip2718Error::UnexpectedType(ty))? { + ScrollTxType::Eip2930 => Ok(Self::Eip2930(TxEip2930::rlp_decode_signed(buf)?)), + ScrollTxType::Eip1559 => Ok(Self::Eip1559(TxEip1559::rlp_decode_signed(buf)?)), + ScrollTxType::Eip7702 => Ok(Self::Eip7702(TxEip7702::rlp_decode_signed(buf)?)), + ScrollTxType::L1Message => Ok(Self::L1Message(TxL1Message::decode(buf)?.seal_slow())), + ScrollTxType::Legacy => { + Err(alloy_rlp::Error::Custom("type-0 eip2718 transactions are not supported") + .into()) + } + } + } + + fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> { + Ok(Self::Legacy(TxLegacy::rlp_decode_signed(buf)?)) + } +} + +impl Encodable2718 for ScrollTxEnvelope { + fn type_flag(&self) -> Option<u8> { + match self { + Self::Legacy(_) => None, + Self::Eip2930(_) => Some(ScrollTxType::Eip2930 as u8), + Self::Eip1559(_) => Some(ScrollTxType::Eip1559 as u8), + Self::Eip7702(_) => Some(ScrollTxType::Eip7702 as u8), + Self::L1Message(_) => Some(ScrollTxType::L1Message as u8), + } + } + + fn encode_2718_len(&self) -> usize { + self.eip2718_encoded_length() + } + + fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) { + match self { + // Legacy transactions have no difference between network and 2718 + Self::Legacy(tx) => tx.eip2718_encode(out), + Self::Eip2930(tx) => { + tx.eip2718_encode(out); + } + Self::Eip1559(tx) => { + tx.eip2718_encode(out); + } + Self::Eip7702(tx) => { + tx.eip2718_encode(out); + } + Self::L1Message(tx) => { + tx.eip2718_encode(out); + } + } + } + + fn trie_hash(&self) -> B256 { + match self { + Self::Legacy(tx) => *tx.hash(), + Self::Eip2930(tx) => *tx.hash(), + Self::Eip1559(tx) => *tx.hash(), + Self::Eip7702(tx) => *tx.hash(), + Self::L1Message(tx) => tx.seal(), + } + } +} + +#[cfg(feature = "k256")] +impl alloy_consensus::transaction::SignerRecoverable for ScrollTxEnvelope { + fn recover_signer(&self) -> Result<Address, alloy_consensus::crypto::RecoveryError> { + let signature_hash = match self { + Self::Legacy(tx) => tx.signature_hash(), + Self::Eip2930(tx) => tx.signature_hash(), + Self::Eip1559(tx) => tx.signature_hash(), + Self::Eip7702(tx) => tx.signature_hash(), + Self::L1Message(tx) => return Ok(tx.sender), + }; + let signature = self.signature().expect("handled L1 message in previous match"); + alloy_consensus::crypto::secp256k1::recover_signer(&signature, signature_hash) + } + + fn recover_signer_unchecked(&self) -> Result<Address, alloy_consensus::crypto::RecoveryError> { + let signature_hash = match self { + Self::Legacy(tx) => tx.signature_hash(), + Self::Eip2930(tx) => tx.signature_hash(), + Self::Eip1559(tx) => tx.signature_hash(), + Self::Eip7702(tx) => tx.signature_hash(), + Self::L1Message(tx) => return Ok(tx.sender), + }; + let signature = self.signature().expect("handled L1 message in previous match"); + alloy_consensus::crypto::secp256k1::recover_signer_unchecked(&signature, signature_hash) + } +} + +#[cfg(feature = "serde")] +mod serde_from { + //! NB: Why do we need this? + //! + //! Because the tag may be missing, we need an abstraction over tagged (with + //! type) and untagged (always legacy). This is [`MaybeTaggedTxEnvelope`]. + //! + //! The tagged variant is [`TaggedTxEnvelope`], which always has a type tag. + //! + //! We serialize via [`TaggedTxEnvelope`] and deserialize via + //! [`MaybeTaggedTxEnvelope`]. + use super::*; + + #[derive(Debug, serde::Deserialize)] + #[serde(untagged)] + pub(crate) enum MaybeTaggedTxEnvelope { + Tagged(TaggedTxEnvelope), + #[serde(with = "alloy_consensus::transaction::signed_legacy_serde")] + Untagged(Signed<TxLegacy>), + } + + #[derive(Debug, serde::Serialize, serde::Deserialize)] + #[serde(tag = "type")] + pub(crate) enum TaggedTxEnvelope { + #[serde( + rename = "0x0", + alias = "0x00", + with = "alloy_consensus::transaction::signed_legacy_serde" + )] + Legacy(Signed<TxLegacy>), + #[serde(rename = "0x1", alias = "0x01")] + Eip2930(Signed<TxEip2930>), + #[serde(rename = "0x2", alias = "0x02")] + Eip1559(Signed<TxEip1559>), + #[serde(rename = "0x4", alias = "0x04")] + Eip7702(Signed<TxEip7702>), + #[serde( + rename = "0x7e", + alias = "0x7E", + serialize_with = "crate::serde_l1_message_tx_rpc" + )] + L1Message(Sealed<TxL1Message>), + } + + impl From<MaybeTaggedTxEnvelope> for ScrollTxEnvelope { + fn from(value: MaybeTaggedTxEnvelope) -> Self { + match value { + MaybeTaggedTxEnvelope::Tagged(tagged) => tagged.into(), + MaybeTaggedTxEnvelope::Untagged(tx) => Self::Legacy(tx), + } + } + } + + impl From<TaggedTxEnvelope> for ScrollTxEnvelope { + fn from(value: TaggedTxEnvelope) -> Self { + match value { + TaggedTxEnvelope::Legacy(signed) => Self::Legacy(signed), + TaggedTxEnvelope::Eip2930(signed) => Self::Eip2930(signed), + TaggedTxEnvelope::Eip1559(signed) => Self::Eip1559(signed), + TaggedTxEnvelope::Eip7702(signed) => Self::Eip7702(signed), + TaggedTxEnvelope::L1Message(tx) => Self::L1Message(tx), + } + } + } + + impl From<ScrollTxEnvelope> for TaggedTxEnvelope { + fn from(value: ScrollTxEnvelope) -> Self { + match value { + ScrollTxEnvelope::Legacy(signed) => Self::Legacy(signed), + ScrollTxEnvelope::Eip2930(signed) => Self::Eip2930(signed), + ScrollTxEnvelope::Eip1559(signed) => Self::Eip1559(signed), + ScrollTxEnvelope::Eip7702(signed) => Self::Eip7702(signed), + ScrollTxEnvelope::L1Message(tx) => Self::L1Message(tx), + } + } + } +} + +/// Bincode-compatible serde implementation for `ScrollTxEnvelope`. +#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))] +pub(super) mod serde_bincode_compat { + use crate::TxL1Message; + + use alloy_consensus::{ + transaction::serde_bincode_compat::{TxEip1559, TxEip2930, TxEip7702, TxLegacy}, + Sealed, Signed, + }; + use alloy_primitives::{Signature, B256}; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use serde_with::{DeserializeAs, SerializeAs}; + + /// Bincode-compatible representation of an `ScrollTxEnvelope`. + #[derive(Debug, Serialize, Deserialize)] + pub enum ScrollTxEnvelope<'a> { + /// Legacy variant. + Legacy { + /// Transaction signature. + signature: Signature, + /// Borrowed legacy transaction data. + transaction: TxLegacy<'a>, + }, + /// EIP-2930 variant. + Eip2930 { + /// Transaction signature. + signature: Signature, + /// Borrowed EIP-2930 transaction data. + transaction: TxEip2930<'a>, + }, + /// EIP-1559 variant. + Eip1559 { + /// Transaction signature. + signature: Signature, + /// Borrowed EIP-1559 transaction data. + transaction: TxEip1559<'a>, + }, + /// EIP-7702 variant. + Eip7702 { + /// Transaction signature. + signature: Signature, + /// Borrowed EIP-7702 transaction data. + transaction: TxEip7702<'a>, + }, + /// L1 message variant. + TxL1Message { + /// Precomputed hash. + hash: B256, + /// Borrowed deposit transaction data. + transaction: TxL1Message, + }, + } + + impl<'a> From<&'a super::ScrollTxEnvelope> for ScrollTxEnvelope<'a> { + fn from(value: &'a super::ScrollTxEnvelope) -> Self { + match value { + super::ScrollTxEnvelope::Legacy(signed_legacy) => Self::Legacy { + signature: *signed_legacy.signature(), + transaction: signed_legacy.tx().into(), + }, + super::ScrollTxEnvelope::Eip2930(signed_2930) => Self::Eip2930 { + signature: *signed_2930.signature(), + transaction: signed_2930.tx().into(), + }, + super::ScrollTxEnvelope::Eip1559(signed_1559) => Self::Eip1559 { + signature: *signed_1559.signature(), + transaction: signed_1559.tx().into(), + }, + super::ScrollTxEnvelope::Eip7702(signed_7702) => Self::Eip7702 { + signature: *signed_7702.signature(), + transaction: signed_7702.tx().into(), + }, + super::ScrollTxEnvelope::L1Message(sealed_l1_message) => Self::TxL1Message { + hash: sealed_l1_message.seal(), + transaction: sealed_l1_message.inner().clone(), + }, + } + } + } + + impl<'a> From<ScrollTxEnvelope<'a>> for super::ScrollTxEnvelope { + fn from(value: ScrollTxEnvelope<'a>) -> Self { + match value { + ScrollTxEnvelope::Legacy { signature, transaction } => { + Self::Legacy(Signed::new_unhashed(transaction.into(), signature)) + } + ScrollTxEnvelope::Eip2930 { signature, transaction } => { + Self::Eip2930(Signed::new_unhashed(transaction.into(), signature)) + } + ScrollTxEnvelope::Eip1559 { signature, transaction } => { + Self::Eip1559(Signed::new_unhashed(transaction.into(), signature)) + } + ScrollTxEnvelope::Eip7702 { signature, transaction } => { + Self::Eip7702(Signed::new_unhashed(transaction.into(), signature)) + } + ScrollTxEnvelope::TxL1Message { hash, transaction } => { + Self::L1Message(Sealed::new_unchecked(transaction, hash)) + } + } + } + } + + impl SerializeAs<super::ScrollTxEnvelope> for ScrollTxEnvelope<'_> { + fn serialize_as<S>( + source: &super::ScrollTxEnvelope, + serializer: S, + ) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let borrowed = ScrollTxEnvelope::from(source); + borrowed.serialize(serializer) + } + } + + impl<'de> DeserializeAs<'de, super::ScrollTxEnvelope> for ScrollTxEnvelope<'de> { + fn deserialize_as<D>(deserializer: D) -> Result<super::ScrollTxEnvelope, D::Error> + where + D: Deserializer<'de>, + { + let borrowed = ScrollTxEnvelope::deserialize(deserializer)?; + Ok(borrowed.into()) + } + } + + #[cfg(test)] + mod tests { + use super::*; + use arbitrary::Arbitrary; + use rand::Rng; + use serde::{Deserialize, Serialize}; + use serde_with::serde_as; + + /// Tests a bincode round-trip for `ScrollTxEnvelope` using an arbitrary instance. + #[test] + fn test_scroll_tx_envelope_bincode_roundtrip_arbitrary() { + #[serde_as] + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + struct Data { + // Use the bincode-compatible representation defined in this module. + #[serde_as(as = "ScrollTxEnvelope<'_>")] + envelope: super::super::ScrollTxEnvelope, + } + + let mut bytes = [0u8; 1024]; + rand::rng().fill(bytes.as_mut_slice()); + let data = Data { + envelope: super::super::ScrollTxEnvelope::arbitrary( + &mut arbitrary::Unstructured::new(&bytes), + ) + .unwrap(), + }; + + let encoded = bincode::serialize(&data).unwrap(); + let decoded: Data = bincode::deserialize(&encoded).unwrap(); + assert_eq!(decoded, data); + } + } +} + +#[cfg(test)] +mod tests { + extern crate alloc; + use super::*; + use alloc::vec; + use alloy_primitives::{hex, Address, Bytes, U256}; + + #[test] + fn test_tx_gas_limit() { + let tx = TxL1Message { gas_limit: 1, ..Default::default() }; + let tx_envelope = ScrollTxEnvelope::L1Message(tx.seal_slow()); + assert_eq!(tx_envelope.gas_limit(), 1); + } + + #[test] + fn test_encode_decode_l1_message() { + let tx = TxL1Message { + queue_index: 1, + gas_limit: 2, + to: Address::left_padding_from(&[3]), + sender: Address::left_padding_from(&[4]), + value: U256::from(4_u64), + input: Bytes::from(vec![5]), + }; + let tx_envelope = ScrollTxEnvelope::L1Message(tx.seal_slow()); + let encoded = tx_envelope.encoded_2718(); + let decoded = ScrollTxEnvelope::decode_2718(&mut encoded.as_ref()).unwrap(); + assert_eq!(encoded.len(), tx_envelope.encode_2718_len()); + assert_eq!(decoded, tx_envelope); + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_roundtrip_l1_message() { + let tx = TxL1Message { + queue_index: 11, + gas_limit: u64::MAX, + sender: Address::random(), + to: Address::random(), + value: U256::MAX, + input: Bytes::new(), + }; + let tx_envelope = ScrollTxEnvelope::L1Message(tx.seal_slow()); + + let serialized = serde_json::to_string(&tx_envelope).unwrap(); + let deserialized: ScrollTxEnvelope = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(tx_envelope, deserialized); + } + + #[test] + fn eip2718_l1_message_decode() { + // <https://scrollscan.com/tx/0xace7103cc22a372c81cda04e15442a721cd3d5d64eda2e1578ba310d91597d97> + let b = hex!("7ef9015a830e7991831e848094781e90f1c8fc4611c9b7497c3b47f99ef6969cbc80b901248ef1332e000000000000000000000000c186fa914353c44b2e33ebe05f21846f1048beda0000000000000000000000003bad7ad0728f9917d1bf08af5782dcbd516cdd96000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e799100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044493a4f8411b3f3d662006b9bf68884e71f1fc0f8ea04e4cb188354738202c3e34a473b93000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000947885bcbd5cecef1336b5300fb5186a12ddd8c478"); + + let tx = ScrollTxEnvelope::decode_2718(&mut b[..].as_ref()).unwrap(); + tx.as_l1_message().unwrap(); + } + + #[test] + fn eip1559_decode() { + use alloy_consensus::SignableTransaction; + use alloy_primitives::Signature; + let tx = TxEip1559 { + chain_id: 1u64, + nonce: 2, + max_fee_per_gas: 3, + max_priority_fee_per_gas: 4, + gas_limit: 5, + to: Address::left_padding_from(&[6]).into(), + value: U256::from(7_u64), + input: vec![8].into(), + access_list: Default::default(), + }; + let sig = Signature::test_signature(); + let tx_signed = tx.into_signed(sig); + let envelope: ScrollTxEnvelope = tx_signed.into(); + let encoded = envelope.encoded_2718(); + let mut slice = encoded.as_slice(); + let decoded = ScrollTxEnvelope::decode_2718(&mut slice).unwrap(); + assert!(matches!(decoded, ScrollTxEnvelope::Eip1559(_))); + } +}
diff --git reth/crates/scroll/alloy/consensus/src/transaction/l1_message.rs scroll-reth/crates/scroll/alloy/consensus/src/transaction/l1_message.rs new file mode 100644 index 0000000000000000000000000000000000000000..0c200904fbc2c86af0eec9940762d31869dd44f8 --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/transaction/l1_message.rs @@ -0,0 +1,455 @@ +//! Scroll L1 message transaction + +use crate::ScrollTxType; +use std::vec::Vec; + +use alloy_consensus::{Sealable, Transaction, Typed2718}; +use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718}; +use alloy_primitives::{ + keccak256, + private::alloy_rlp::{Encodable, Header}, + Address, Bytes, ChainId, Signature, TxHash, TxKind, B256, U256, +}; +use alloy_rlp::Decodable; +#[cfg(any(test, feature = "reth-codec"))] +use {reth_codecs::Compact, reth_codecs_derive::add_arbitrary_tests}; + +/// L1 message transaction type id, 0x7e in hex. +pub const L1_MESSAGE_TRANSACTION_TYPE: u8 = 126; + +/// A message transaction sent from the settlement layer to the L2 for execution. +/// +/// The signature of the L1 message is already verified on the L1 and as such doesn't contain +/// a signature field. Gas for the transaction execution on Scroll is already paid for on the L1. +/// +/// # Bincode compatibility +/// +/// `bincode` crate doesn't work with optionally serializable serde fields and some of the execution +/// types require optional serialization for RPC compatibility. Since `TxL1Message` doesn't +/// contain optionally serializable fields, no `bincode` compatible bridge implementation is +/// required. +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(any(test, feature = "serde"), serde(rename_all = "camelCase"))] +#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] +#[cfg_attr(any(test, feature = "reth-codec"), derive(Compact))] +#[cfg_attr(any(test, feature = "reth-codec"), add_arbitrary_tests(compact, rlp))] +pub struct TxL1Message { + /// The queue index of the message in the L1 contract queue. + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] + pub queue_index: u64, + /// The gas limit for the transaction. Gas is paid for when message is sent from the L1. + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity", rename = "gas"))] + pub gas_limit: u64, + /// The destination for the transaction. `Address` is used in place of `TxKind` since contract + /// creations aren't allowed via L1 message transactions. + pub to: Address, + /// The value sent. + pub value: U256, + /// The L1 sender of the transaction. + pub sender: Address, + /// The input of the transaction. + pub input: Bytes, +} + +impl TxL1Message { + /// Returns an empty signature for the [`TxL1Message`], which don't include a signature. + pub const fn signature() -> Signature { + Signature::new(U256::ZERO, U256::ZERO, false) + } + + /// Decodes the inner [`TxL1Message`] fields from RLP bytes. + /// + /// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following + /// RLP fields in the following order: + /// + /// - `queue_index` + /// - `gas_limit` + /// - `to` + /// - `value` + /// - `input` + /// - `sender` + pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result<Self> { + Ok(Self { + queue_index: Decodable::decode(buf)?, + gas_limit: Decodable::decode(buf)?, + to: Decodable::decode(buf)?, + value: Decodable::decode(buf)?, + input: Decodable::decode(buf)?, + sender: Decodable::decode(buf)?, + }) + } + + /// Decodes the transaction from RLP bytes. + pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> { + let header = Header::decode(buf)?; + if !header.list { + return Err(alloy_rlp::Error::UnexpectedString); + } + let remaining = buf.len(); + + let this = Self::rlp_decode_fields(buf)?; + + if buf.len() + header.payload_length != remaining { + return Err(alloy_rlp::Error::ListLengthMismatch { + expected: header.payload_length, + got: remaining - buf.len(), + }); + } + + Ok(this) + } + + /// Outputs the length of the transaction's fields, without a RLP header. + pub fn rlp_encoded_fields_length(&self) -> usize { + self.queue_index.length() + + self.gas_limit.length() + + self.to.length() + + self.value.length() + + self.input.0.length() + + self.sender.length() + } + + /// Encode the fields of the transaction without a RLP header. + /// <https://github.com/scroll-tech/go-ethereum/blob/9fff27e4f34fb5097100ed76ee725ce056267f4b/core/types/l1_message_tx.go#L12-L19> + pub fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) { + self.queue_index.encode(out); + self.gas_limit.encode(out); + self.to.encode(out); + self.value.encode(out); + self.input.encode(out); + self.sender.encode(out); + } + + pub(crate) const fn tx_type(&self) -> u8 { + L1_MESSAGE_TRANSACTION_TYPE + } + + /// Create a RLP header for the transaction. + fn rlp_header(&self) -> Header { + Header { list: true, payload_length: self.rlp_encoded_fields_length() } + } + + /// RLP encodes the transaction. + pub fn rlp_encode(&self, out: &mut dyn alloy_rlp::BufMut) { + self.rlp_header().encode(out); + self.rlp_encode_fields(out); + } + + /// Get the length of the transaction when RLP encoded. + pub fn rlp_encoded_length(&self) -> usize { + self.rlp_header().length_with_payload() + } + + /// Get the length of the transaction when EIP-2718 encoded. This is the + /// 1 byte type flag + the length of the RLP encoded transaction. + pub fn eip2718_encoded_length(&self) -> usize { + self.rlp_encoded_length() + 1 + } + + /// EIP-2718 encode the transaction. + pub fn eip2718_encode(&self, out: &mut dyn alloy_rlp::BufMut) { + out.put_u8(L1_MESSAGE_TRANSACTION_TYPE); + self.rlp_encode(out) + } + + /// Calculates the in-memory size of the [`TxL1Message`] transaction. + #[inline] + pub fn size(&self) -> usize { + size_of::<u64>() + // queue_index + size_of::<u64>() + // gas_limit + size_of::<Address>() + // to + size_of::<U256>() + // value + self.input.len() + // input + size_of::<Address>() // sender + } + + /// Calculates the hash of the [`TxL1Message`] transaction. + pub fn tx_hash(&self) -> TxHash { + let mut buf = Vec::with_capacity(self.eip2718_encoded_length()); + self.eip2718_encode(&mut buf); + keccak256(&buf) + } +} + +impl Typed2718 for TxL1Message { + fn ty(&self) -> u8 { + ScrollTxType::L1Message as u8 + } +} + +impl Encodable2718 for TxL1Message { + fn type_flag(&self) -> Option<u8> { + Some(self.tx_type()) + } + + fn encode_2718_len(&self) -> usize { + self.eip2718_encoded_length() + } + + fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) { + out.put_u8(self.tx_type()); + self.rlp_encode(out); + } +} + +impl Decodable2718 for TxL1Message { + fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> { + if ty != L1_MESSAGE_TRANSACTION_TYPE { + return Err(Eip2718Error::UnexpectedType(ty)); + } + let tx = Self::rlp_decode(buf)?; + Ok(tx) + } + + fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> { + let tx = Self::decode(buf)?; + Ok(tx) + } +} + +impl Encodable for TxL1Message { + fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { + self.rlp_encode(out) + } + + fn length(&self) -> usize { + self.rlp_encoded_length() + } +} + +impl Decodable for TxL1Message { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> { + Self::rlp_decode(buf) + } +} + +impl Transaction for TxL1Message { + fn chain_id(&self) -> Option<ChainId> { + None + } + + fn nonce(&self) -> u64 { + 0u64 + } + + fn gas_limit(&self) -> u64 { + self.gas_limit + } + + fn gas_price(&self) -> Option<u128> { + None + } + + fn max_fee_per_gas(&self) -> u128 { + 0 + } + + fn max_priority_fee_per_gas(&self) -> Option<u128> { + None + } + + fn max_fee_per_blob_gas(&self) -> Option<u128> { + None + } + + fn priority_fee_or_price(&self) -> u128 { + 0 + } + + fn effective_gas_price(&self, _base_fee: Option<u64>) -> u128 { + 0 + } + + fn is_dynamic_fee(&self) -> bool { + false + } + + fn kind(&self) -> TxKind { + TxKind::Call(self.to) + } + + fn is_create(&self) -> bool { + false + } + + fn value(&self) -> U256 { + self.value + } + + fn input(&self) -> &Bytes { + &self.input + } + + fn access_list(&self) -> Option<&alloy_eips::eip2930::AccessList> { + None + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + None + } + + fn authorization_list(&self) -> Option<&[alloy_eips::eip7702::SignedAuthorization]> { + None + } +} + +impl Sealable for TxL1Message { + fn hash_slow(&self) -> B256 { + self.tx_hash() + } +} + +/// Scroll specific transaction fields +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(any(test, feature = "serde"), serde(rename_all = "camelCase"))] +pub struct ScrollL1MessageTransactionFields { + /// The index of the transaction in the message queue. + #[cfg_attr(any(test, feature = "serde"), serde(with = "alloy_serde::quantity"))] + pub queue_index: u64, + /// The sender of the transaction on the L1. + pub sender: Address, +} + +/// L1 message transactions don't have a signature, however, we include an empty signature in the +/// response for better compatibility. +/// +/// This function can be used as `serialize_with` serde attribute for the [`TxL1Message`] and will +/// flatten [`TxL1Message::signature`] into response. +/// +/// <https://github.com/scroll-tech/go-ethereum/blob/develop/core/types/l1_message_tx.go#L51>. +#[cfg(feature = "serde")] +pub fn serde_l1_message_tx_rpc<T: serde::Serialize, S: serde::Serializer>( + value: &T, + serializer: S, +) -> Result<S::Ok, S::Error> { + use serde::Serialize; + + #[derive(Serialize)] + struct SerdeHelper<'a, T> { + #[serde(flatten)] + value: &'a T, + #[serde(flatten)] + signature: Signature, + } + + SerdeHelper { value, signature: TxL1Message::signature() }.serialize(serializer) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_eips::eip2718::Encodable2718; + use alloy_primitives::{address, bytes, hex, Bytes, U256}; + use alloy_rlp::BytesMut; + use arbitrary::Arbitrary; + use rand::Rng; + use reth_codecs::{test_utils::UnusedBits, validate_bitflag_backwards_compat}; + + #[test] + fn test_rlp_roundtrip() { + // <https://scrollscan.com/tx/0xace7103cc22a372c81cda04e15442a721cd3d5d64eda2e1578ba310d91597d97> + let bytes = Bytes::from_static(&hex!("7ef9015a830e7991831e848094781e90f1c8fc4611c9b7497c3b47f99ef6969cbc80b901248ef1332e000000000000000000000000c186fa914353c44b2e33ebe05f21846f1048beda0000000000000000000000003bad7ad0728f9917d1bf08af5782dcbd516cdd96000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e799100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044493a4f8411b3f3d662006b9bf68884e71f1fc0f8ea04e4cb188354738202c3e34a473b93000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000947885bcbd5cecef1336b5300fb5186a12ddd8c478")); + let tx_a = TxL1Message::decode(&mut bytes[1..].as_ref()).unwrap(); + let mut buf_a = BytesMut::default(); + tx_a.encode(&mut buf_a); + assert_eq!(&buf_a[..], &bytes[1..]); + } + + #[test] + fn test_encode_decode_fields() { + let original = TxL1Message { + queue_index: 100, + gas_limit: 0, + to: Address::default(), + value: U256::default(), + sender: Address::default(), + input: Bytes::default(), + }; + let mut buffer = BytesMut::new(); + original.rlp_encode_fields(&mut buffer); + let decoded = TxL1Message::rlp_decode_fields(&mut &buffer[..]).expect("Failed to decode"); + + assert_eq!(original, decoded); + } + + #[test] + fn test_encode_with_and_without_header() { + let tx_deposit = TxL1Message { + queue_index: 0, + gas_limit: 50000, + to: Address::default(), + value: U256::default(), + sender: Address::default(), + input: Bytes::default(), + }; + + let mut buffer_with_header = BytesMut::new(); + tx_deposit.encode(&mut buffer_with_header); + + let mut buffer_without_header = BytesMut::new(); + tx_deposit.rlp_encode_fields(&mut buffer_without_header); + + assert!(buffer_with_header.len() > buffer_without_header.len()); + } + + #[test] + fn test_payload_length() { + let tx_deposit = TxL1Message { + queue_index: 0, + gas_limit: 50000, + to: Address::default(), + value: U256::default(), + sender: Address::default(), + input: Bytes::default(), + }; + + assert!(tx_deposit.size() > tx_deposit.rlp_encoded_fields_length()); + } + + #[test] + fn test_deserialize_hex_to_u64() { + let rpc_tx = r#"{"gas":"0x1e8480","input":"0x8ef1332e000000000000000000000000c186fa914353c44b2e33ebe05f21846f1048beda0000000000000000000000003bad7ad0728f9917d1bf08af5782dcbd516cdd96000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e7ba000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044493a4f846ffc1507cbfe98a2b0ba1f06ea7e4eb749c001f78f6cb5540daa556a0566322a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","to":"0x781e90f1c8fc4611c9b7497c3b47f99ef6969cbc","value":"0x0","sender":"0x7885bcbd5cecef1336b5300fb5186a12ddd8c478","queueIndex":"0xe7ba0"}"#; + // let obj: TxL1Message = serde_json::from_str(rpc_tx).unwrap(); + let obj = serde_json::from_str::<TxL1Message>(rpc_tx).unwrap(); + assert_eq!(obj.queue_index, 0xe7ba0); + } + + #[test] + fn test_bincode_roundtrip() { + let mut bytes = [0u8; 1024]; + rand::rng().fill(bytes.as_mut_slice()); + let tx = TxL1Message::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(); + + let encoded = bincode::serialize(&tx).unwrap(); + let decoded: TxL1Message = bincode::deserialize(&encoded).unwrap(); + assert_eq!(decoded, tx); + } + + #[test] + fn test_eip2718_encode() { + let tx = + TxL1Message { + queue_index: 947883, + gas_limit: 2000000, + to: address!("781e90f1c8fc4611c9b7497c3b47f99ef6969cbc"), + value: U256::ZERO, + sender: address!("7885bcbd5cecef1336b5300fb5186a12ddd8c478"), + input: bytes!("8ef1332e000000000000000000000000c186fa914353c44b2e33ebe05f21846f1048beda0000000000000000000000003bad7ad0728f9917d1bf08af5782dcbd516cdd96000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e76ab00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044493a4f84f464e58d4bfa93bcc57abfb14dbe1b8ff46cd132b5709aab227f269727943d2f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + } + ; + let bytes = Bytes::from_static(&hex!("7ef9015a830e76ab831e848094781e90f1c8fc4611c9b7497c3b47f99ef6969cbc80b901248ef1332e000000000000000000000000c186fa914353c44b2e33ebe05f21846f1048beda0000000000000000000000003bad7ad0728f9917d1bf08af5782dcbd516cdd96000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e76ab00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044493a4f84f464e58d4bfa93bcc57abfb14dbe1b8ff46cd132b5709aab227f269727943d2f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000947885bcbd5cecef1336b5300fb5186a12ddd8c478")); + + let mut encoded = BytesMut::default(); + tx.encode_2718(&mut encoded); + + assert_eq!(encoded, bytes.as_ref()) + } + + #[test] + fn test_compaction_backwards_compatibility() { + assert_eq!(TxL1Message::bitflag_encoded_bytes(), 2); + validate_bitflag_backwards_compat!(TxL1Message, UnusedBits::NotZero); + } +}
diff --git reth/crates/scroll/alloy/consensus/src/transaction/meta.rs scroll-reth/crates/scroll/alloy/consensus/src/transaction/meta.rs new file mode 100644 index 0000000000000000000000000000000000000000..d1ffc5f5acd663a9c042645dbb4d6e576edebfb2 --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/transaction/meta.rs @@ -0,0 +1,28 @@ +use alloy_consensus::transaction::TransactionInfo; +use alloy_primitives::U256; + +/// Additional receipt metadata required for Scroll transactions. +/// +/// These fields are used to provide additional context for in RPC responses. +#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)] +pub struct ScrollAdditionalInfo { + /// Only present in RPC responses. + pub l1_fee: U256, +} + +/// Additional fields in the context of a block that contains this transaction. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ScrollTransactionInfo { + /// Additional transaction information. + pub inner: TransactionInfo, + /// Additional metadata for Scroll. + pub additional_info: ScrollAdditionalInfo, +} + +impl ScrollTransactionInfo { + /// Creates a new [`ScrollTransactionInfo`] with the given [`TransactionInfo`] and + /// [`ScrollAdditionalInfo`]. + pub const fn new(inner: TransactionInfo, additional_info: ScrollAdditionalInfo) -> Self { + Self { inner, additional_info } + } +}
diff --git reth/crates/scroll/alloy/consensus/src/transaction/mod.rs scroll-reth/crates/scroll/alloy/consensus/src/transaction/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..b2f08383ce63039af0eacf994da0805526ca9485 --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/transaction/mod.rs @@ -0,0 +1,28 @@ +//! Transaction types for Scroll. + +mod tx_type; +pub use tx_type::{ScrollTxType, L1_MESSAGE_TX_TYPE_ID}; + +mod envelope; +pub use envelope::{ScrollTransaction, ScrollTxEnvelope}; + +mod l1_message; +pub use l1_message::{ScrollL1MessageTransactionFields, TxL1Message, L1_MESSAGE_TRANSACTION_TYPE}; + +mod meta; +pub use meta::{ScrollAdditionalInfo, ScrollTransactionInfo}; + +mod typed; +pub use typed::ScrollTypedTransaction; + +mod pooled; +pub use pooled::ScrollPooledTransaction; + +#[cfg(feature = "serde")] +pub use l1_message::serde_l1_message_tx_rpc; + +/// Bincode-compatible serde implementations for transaction types. +#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))] +pub(super) mod serde_bincode_compat { + pub use super::envelope::serde_bincode_compat::*; +}
diff --git reth/crates/scroll/alloy/consensus/src/transaction/pooled.rs scroll-reth/crates/scroll/alloy/consensus/src/transaction/pooled.rs new file mode 100644 index 0000000000000000000000000000000000000000..f59dca54ee2e909e6b1d436ccbc33ce740a3b3f2 --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/transaction/pooled.rs @@ -0,0 +1,552 @@ +//! Defines the exact transaction variants that are allowed to be propagated over the eth p2p +//! protocol in op. + +use crate::{ScrollTxEnvelope, ScrollTxType}; +use alloy_consensus::{ + error::ValueError, + transaction::{RlpEcdsaDecodableTx, TxEip1559, TxEip2930, TxLegacy}, + SignableTransaction, Signed, Transaction, TxEip7702, TxEnvelope, Typed2718, +}; +use alloy_eips::{ + eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718}, + eip2930::AccessList, + eip7702::SignedAuthorization, +}; +use alloy_primitives::{bytes, Bytes, ChainId, Signature, TxHash, TxKind, B256, U256}; +use alloy_rlp::{Decodable, Encodable, Header}; +use core::hash::{Hash, Hasher}; + +/// All possible transactions that can be included in a response to `GetPooledTransactions`. +/// A response to `GetPooledTransactions`. This can include a typed signed transaction, but cannot +/// include a deposit transaction or EIP-4844 transaction. +/// +/// The difference between this and the [`ScrollTxEnvelope`] is that this type does not have the +/// L1 message variant, which is not expected to be pooled. +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(all(any(test, feature = "arbitrary"), feature = "k256"), derive(arbitrary::Arbitrary))] +pub enum ScrollPooledTransaction { + /// An untagged [`TxLegacy`]. + Legacy(Signed<TxLegacy>), + /// A [`TxEip2930`] transaction tagged with type 1. + Eip2930(Signed<TxEip2930>), + /// A [`TxEip1559`] transaction tagged with type 2. + Eip1559(Signed<TxEip1559>), + /// A [`TxEip7702`] transaction tagged with type 4. + Eip7702(Signed<TxEip7702>), +} + +impl ScrollPooledTransaction { + /// Heavy operation that returns the signature hash over rlp encoded transaction. It is only + /// for signature signing or signer recovery. + pub fn signature_hash(&self) -> B256 { + match self { + Self::Legacy(tx) => tx.signature_hash(), + Self::Eip2930(tx) => tx.signature_hash(), + Self::Eip1559(tx) => tx.signature_hash(), + Self::Eip7702(tx) => tx.signature_hash(), + } + } + + /// Reference to transaction hash. Used to identify transaction. + pub fn hash(&self) -> &TxHash { + match self { + Self::Legacy(tx) => tx.hash(), + Self::Eip2930(tx) => tx.hash(), + Self::Eip1559(tx) => tx.hash(), + Self::Eip7702(tx) => tx.hash(), + } + } + + /// Returns the signature of the transaction. + pub const fn signature(&self) -> &Signature { + match self { + Self::Legacy(tx) => tx.signature(), + Self::Eip2930(tx) => tx.signature(), + Self::Eip1559(tx) => tx.signature(), + Self::Eip7702(tx) => tx.signature(), + } + } + + /// The length of the 2718 encoded envelope in network format. This is the + /// length of the header + the length of the type flag and inner encoding. + fn network_len(&self) -> usize { + let mut payload_length = self.encode_2718_len(); + if !self.is_legacy() { + payload_length += Header { list: false, payload_length }.length(); + } + + payload_length + } + + /// Recover the signer of the transaction. + #[cfg(feature = "k256")] + pub fn recover_signer( + &self, + ) -> Result<alloy_primitives::Address, alloy_primitives::SignatureError> { + match self { + Self::Legacy(tx) => tx.recover_signer(), + Self::Eip2930(tx) => tx.recover_signer(), + Self::Eip1559(tx) => tx.recover_signer(), + Self::Eip7702(tx) => tx.recover_signer(), + } + } + + /// This encodes the transaction _without_ the signature, and is only suitable for creating a + /// hash intended for signing. + pub fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) { + match self { + Self::Legacy(tx) => tx.tx().encode_for_signing(out), + Self::Eip2930(tx) => tx.tx().encode_for_signing(out), + Self::Eip1559(tx) => tx.tx().encode_for_signing(out), + Self::Eip7702(tx) => tx.tx().encode_for_signing(out), + } + } + + /// Converts the transaction into the ethereum [`TxEnvelope`]. + pub fn into_envelope(self) -> TxEnvelope { + match self { + Self::Legacy(tx) => tx.into(), + Self::Eip2930(tx) => tx.into(), + Self::Eip1559(tx) => tx.into(), + Self::Eip7702(tx) => tx.into(), + } + } + + /// Converts the transaction into the scroll [`ScrollTxEnvelope`]. + pub fn into_scroll_envelope(self) -> ScrollTxEnvelope { + match self { + Self::Legacy(tx) => tx.into(), + Self::Eip2930(tx) => tx.into(), + Self::Eip1559(tx) => tx.into(), + Self::Eip7702(tx) => tx.into(), + } + } + + /// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction. + pub const fn as_legacy(&self) -> Option<&TxLegacy> { + match self { + Self::Legacy(tx) => Some(tx.tx()), + _ => None, + } + } + + /// Returns the [`TxEip2930`] variant if the transaction is an EIP-2930 transaction. + pub const fn as_eip2930(&self) -> Option<&TxEip2930> { + match self { + Self::Eip2930(tx) => Some(tx.tx()), + _ => None, + } + } + + /// Returns the [`TxEip1559`] variant if the transaction is an EIP-1559 transaction. + pub const fn as_eip1559(&self) -> Option<&TxEip1559> { + match self { + Self::Eip1559(tx) => Some(tx.tx()), + _ => None, + } + } + + /// Returns the [`TxEip7702`] variant if the transaction is an EIP-1559 transaction. + pub const fn as_eip7702(&self) -> Option<&TxEip7702> { + match self { + Self::Eip7702(tx) => Some(tx.tx()), + _ => None, + } + } +} + +impl From<Signed<TxLegacy>> for ScrollPooledTransaction { + fn from(v: Signed<TxLegacy>) -> Self { + Self::Legacy(v) + } +} + +impl From<Signed<TxEip2930>> for ScrollPooledTransaction { + fn from(v: Signed<TxEip2930>) -> Self { + Self::Eip2930(v) + } +} + +impl From<Signed<TxEip1559>> for ScrollPooledTransaction { + fn from(v: Signed<TxEip1559>) -> Self { + Self::Eip1559(v) + } +} + +impl From<Signed<TxEip7702>> for ScrollPooledTransaction { + fn from(v: Signed<TxEip7702>) -> Self { + Self::Eip7702(v) + } +} + +impl Hash for ScrollPooledTransaction { + fn hash<H: Hasher>(&self, state: &mut H) { + self.trie_hash().hash(state); + } +} + +impl Encodable for ScrollPooledTransaction { + /// This encodes the transaction _with_ the signature, and an rlp header. + /// + /// For legacy transactions, it encodes the transaction data: + /// `rlp(tx-data)` + /// + /// For EIP-2718 typed transactions, it encodes the transaction type followed by the rlp of the + /// transaction: + /// `rlp(tx-type || rlp(tx-data))` + fn encode(&self, out: &mut dyn bytes::BufMut) { + self.network_encode(out); + } + + fn length(&self) -> usize { + self.network_len() + } +} + +impl Decodable for ScrollPooledTransaction { + /// Decodes an enveloped [`ScrollPooledTransaction`]. + /// + /// CAUTION: this expects that `buf` is `rlp(tx_type || rlp(tx-data))` + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> { + Ok(Self::network_decode(buf)?) + } +} + +impl Encodable2718 for ScrollPooledTransaction { + fn type_flag(&self) -> Option<u8> { + match self { + Self::Legacy(_) => None, + Self::Eip2930(_) => Some(0x01), + Self::Eip1559(_) => Some(0x02), + Self::Eip7702(_) => Some(0x04), + } + } + + fn encode_2718_len(&self) -> usize { + match self { + Self::Legacy(tx) => tx.eip2718_encoded_length(), + Self::Eip2930(tx) => tx.eip2718_encoded_length(), + Self::Eip1559(tx) => tx.eip2718_encoded_length(), + Self::Eip7702(tx) => tx.eip2718_encoded_length(), + } + } + + fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) { + match self { + Self::Legacy(tx) => tx.eip2718_encode(out), + Self::Eip2930(tx) => tx.eip2718_encode(out), + Self::Eip1559(tx) => tx.eip2718_encode(out), + Self::Eip7702(tx) => tx.eip2718_encode(out), + } + } + + fn trie_hash(&self) -> B256 { + *self.hash() + } +} + +impl Decodable2718 for ScrollPooledTransaction { + fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> { + match ty.try_into().map_err(|_| alloy_rlp::Error::Custom("unexpected tx type"))? { + ScrollTxType::Eip2930 => Ok(TxEip2930::rlp_decode_signed(buf)?.into()), + ScrollTxType::Eip1559 => Ok(TxEip1559::rlp_decode_signed(buf)?.into()), + ScrollTxType::Eip7702 => Ok(TxEip7702::rlp_decode_signed(buf)?.into()), + ScrollTxType::Legacy => Err(Eip2718Error::UnexpectedType(ScrollTxType::Legacy.into())), + ScrollTxType::L1Message => { + Err(Eip2718Error::UnexpectedType(ScrollTxType::L1Message.into())) + } + } + } + + fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> { + TxLegacy::rlp_decode_signed(buf).map(Into::into).map_err(Into::into) + } +} + +impl Transaction for ScrollPooledTransaction { + fn chain_id(&self) -> Option<ChainId> { + match self { + Self::Legacy(tx) => tx.tx().chain_id(), + Self::Eip2930(tx) => tx.tx().chain_id(), + Self::Eip1559(tx) => tx.tx().chain_id(), + Self::Eip7702(tx) => tx.tx().chain_id(), + } + } + + fn nonce(&self) -> u64 { + match self { + Self::Legacy(tx) => tx.tx().nonce(), + Self::Eip2930(tx) => tx.tx().nonce(), + Self::Eip1559(tx) => tx.tx().nonce(), + Self::Eip7702(tx) => tx.tx().nonce(), + } + } + + fn gas_limit(&self) -> u64 { + match self { + Self::Legacy(tx) => tx.tx().gas_limit(), + Self::Eip2930(tx) => tx.tx().gas_limit(), + Self::Eip1559(tx) => tx.tx().gas_limit(), + Self::Eip7702(tx) => tx.tx().gas_limit(), + } + } + + fn gas_price(&self) -> Option<u128> { + match self { + Self::Legacy(tx) => tx.tx().gas_price(), + Self::Eip2930(tx) => tx.tx().gas_price(), + Self::Eip1559(tx) => tx.tx().gas_price(), + Self::Eip7702(tx) => tx.tx().gas_price(), + } + } + + fn max_fee_per_gas(&self) -> u128 { + match self { + Self::Legacy(tx) => tx.tx().max_fee_per_gas(), + Self::Eip2930(tx) => tx.tx().max_fee_per_gas(), + Self::Eip1559(tx) => tx.tx().max_fee_per_gas(), + Self::Eip7702(tx) => tx.tx().max_fee_per_gas(), + } + } + + fn max_priority_fee_per_gas(&self) -> Option<u128> { + match self { + Self::Legacy(tx) => tx.tx().max_priority_fee_per_gas(), + Self::Eip2930(tx) => tx.tx().max_priority_fee_per_gas(), + Self::Eip1559(tx) => tx.tx().max_priority_fee_per_gas(), + Self::Eip7702(tx) => tx.tx().max_priority_fee_per_gas(), + } + } + + fn max_fee_per_blob_gas(&self) -> Option<u128> { + match self { + Self::Legacy(tx) => tx.tx().max_fee_per_blob_gas(), + Self::Eip2930(tx) => tx.tx().max_fee_per_blob_gas(), + Self::Eip1559(tx) => tx.tx().max_fee_per_blob_gas(), + Self::Eip7702(tx) => tx.tx().max_fee_per_blob_gas(), + } + } + + fn priority_fee_or_price(&self) -> u128 { + match self { + Self::Legacy(tx) => tx.tx().priority_fee_or_price(), + Self::Eip2930(tx) => tx.tx().priority_fee_or_price(), + Self::Eip1559(tx) => tx.tx().priority_fee_or_price(), + Self::Eip7702(tx) => tx.tx().priority_fee_or_price(), + } + } + + fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 { + match self { + Self::Legacy(tx) => tx.tx().effective_gas_price(base_fee), + Self::Eip2930(tx) => tx.tx().effective_gas_price(base_fee), + Self::Eip1559(tx) => tx.tx().effective_gas_price(base_fee), + Self::Eip7702(tx) => tx.tx().effective_gas_price(base_fee), + } + } + + fn is_dynamic_fee(&self) -> bool { + match self { + Self::Legacy(tx) => tx.tx().is_dynamic_fee(), + Self::Eip2930(tx) => tx.tx().is_dynamic_fee(), + Self::Eip1559(tx) => tx.tx().is_dynamic_fee(), + Self::Eip7702(tx) => tx.tx().is_dynamic_fee(), + } + } + + fn kind(&self) -> TxKind { + match self { + Self::Legacy(tx) => tx.tx().kind(), + Self::Eip2930(tx) => tx.tx().kind(), + Self::Eip1559(tx) => tx.tx().kind(), + Self::Eip7702(tx) => tx.tx().kind(), + } + } + + fn is_create(&self) -> bool { + match self { + Self::Legacy(tx) => tx.tx().is_create(), + Self::Eip2930(tx) => tx.tx().is_create(), + Self::Eip1559(tx) => tx.tx().is_create(), + Self::Eip7702(tx) => tx.tx().is_create(), + } + } + + fn value(&self) -> U256 { + match self { + Self::Legacy(tx) => tx.tx().value(), + Self::Eip2930(tx) => tx.tx().value(), + Self::Eip1559(tx) => tx.tx().value(), + Self::Eip7702(tx) => tx.tx().value(), + } + } + + fn input(&self) -> &Bytes { + match self { + Self::Legacy(tx) => tx.tx().input(), + Self::Eip2930(tx) => tx.tx().input(), + Self::Eip1559(tx) => tx.tx().input(), + Self::Eip7702(tx) => tx.tx().input(), + } + } + + fn access_list(&self) -> Option<&AccessList> { + match self { + Self::Legacy(tx) => tx.tx().access_list(), + Self::Eip2930(tx) => tx.tx().access_list(), + Self::Eip1559(tx) => tx.tx().access_list(), + Self::Eip7702(tx) => tx.tx().access_list(), + } + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + match self { + Self::Legacy(tx) => tx.tx().blob_versioned_hashes(), + Self::Eip2930(tx) => tx.tx().blob_versioned_hashes(), + Self::Eip1559(tx) => tx.tx().blob_versioned_hashes(), + Self::Eip7702(tx) => tx.tx().blob_versioned_hashes(), + } + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + match self { + Self::Legacy(tx) => tx.tx().authorization_list(), + Self::Eip2930(tx) => tx.tx().authorization_list(), + Self::Eip1559(tx) => tx.tx().authorization_list(), + Self::Eip7702(tx) => tx.tx().authorization_list(), + } + } +} + +impl Typed2718 for ScrollPooledTransaction { + fn ty(&self) -> u8 { + match self { + Self::Legacy(tx) => tx.tx().ty(), + Self::Eip2930(tx) => tx.tx().ty(), + Self::Eip1559(tx) => tx.tx().ty(), + Self::Eip7702(tx) => tx.tx().ty(), + } + } +} + +impl From<ScrollPooledTransaction> for TxEnvelope { + fn from(tx: ScrollPooledTransaction) -> Self { + tx.into_envelope() + } +} + +impl From<ScrollPooledTransaction> for ScrollTxEnvelope { + fn from(tx: ScrollPooledTransaction) -> Self { + tx.into_scroll_envelope() + } +} + +impl TryFrom<ScrollTxEnvelope> for ScrollPooledTransaction { + type Error = ValueError<ScrollTxEnvelope>; + + fn try_from(value: ScrollTxEnvelope) -> Result<Self, Self::Error> { + value.try_into_pooled() + } +} + +#[cfg(feature = "k256")] +impl alloy_consensus::transaction::SignerRecoverable for ScrollPooledTransaction { + fn recover_signer( + &self, + ) -> Result<alloy_primitives::Address, alloy_consensus::crypto::RecoveryError> { + let signature_hash = self.signature_hash(); + alloy_consensus::crypto::secp256k1::recover_signer(self.signature(), signature_hash) + } + + fn recover_signer_unchecked( + &self, + ) -> Result<alloy_primitives::Address, alloy_consensus::crypto::RecoveryError> { + let signature_hash = self.signature_hash(); + alloy_consensus::crypto::secp256k1::recover_signer_unchecked( + self.signature(), + signature_hash, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{address, hex}; + use bytes::Bytes; + + #[test] + fn invalid_legacy_pooled_decoding_input_too_short() { + let input_too_short = [ + // this should fail because the payload length is longer than expected + &hex!("d90b0280808bc5cd028083c5cdfd9e407c56565656")[..], + // these should fail decoding + // + // The `c1` at the beginning is a list header, and the rest is a valid legacy + // transaction, BUT the payload length of the list header is 1, and the payload is + // obviously longer than one byte. + &hex!("c10b02808083c5cd028883c5cdfd9e407c56565656"), + &hex!("c10b0280808bc5cd028083c5cdfd9e407c56565656"), + // this one is 19 bytes, and the buf is long enough, but the transaction will not + // consume that many bytes. + &hex!("d40b02808083c5cdeb8783c5acfd9e407c5656565656"), + &hex!("d30102808083c5cd02887dc5cdfd9e64fd9e407c56"), + ]; + + for hex_data in &input_too_short { + let input_rlp = &mut &hex_data[..]; + let res = ScrollPooledTransaction::decode(input_rlp); + + assert!( + res.is_err(), + "expected err after decoding rlp input: {:x?}", + Bytes::copy_from_slice(hex_data) + ); + + // this is a legacy tx so we can attempt the same test with decode_enveloped + let input_rlp = &mut &hex_data[..]; + let res = ScrollPooledTransaction::decode_2718(input_rlp); + + assert!( + res.is_err(), + "expected err after decoding enveloped rlp input: {:x?}", + Bytes::copy_from_slice(hex_data) + ); + } + } + + // <https://holesky.etherscan.io/tx/0x7f60faf8a410a80d95f7ffda301d5ab983545913d3d789615df3346579f6c849> + #[test] + fn decode_eip1559_enveloped() { + let data = hex!("02f903d382426882ba09832dc6c0848674742682ed9694714b6a4ea9b94a8a7d9fd362ed72630688c8898c80b90364492d24749189822d8512430d3f3ff7a2ede675ac08265c08e2c56ff6fdaa66dae1cdbe4a5d1d7809f3e99272d067364e597542ac0c369d69e22a6399c3e9bee5da4b07e3f3fdc34c32c3d88aa2268785f3e3f8086df0934b10ef92cfffc2e7f3d90f5e83302e31382e302d64657600000000000000000000000000000000000000000000569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd000000000000000000000000e1e210594771824dad216568b91c9cb4ceed361c00000000000000000000000000000000000000000000000000000000000546e00000000000000000000000000000000000000000000000000000000000e4e1c00000000000000000000000000000000000000000000000000000000065d6750c00000000000000000000000000000000000000000000000000000000000f288000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002cf600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000f1628e56fa6d8c50e5b984a58c0df14de31c7b857ce7ba499945b99252976a93d06dcda6776fc42167fbe71cb59f978f5ef5b12577a90b132d14d9c6efa528076f0161d7bf03643cfc5490ec5084f4a041db7f06c50bd97efa08907ba79ddcac8b890f24d12d8db31abbaaf18985d54f400449ee0559a4452afe53de5853ce090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000c080a01428023fc54a27544abc421d5d017b9a7c5936ad501cbdecd0d9d12d04c1a033a0753104bbf1c87634d6ff3f0ffa0982710612306003eb022363b57994bdef445a" +); + + let res = ScrollPooledTransaction::decode_2718(&mut &data[..]).unwrap(); + assert_eq!(res.to(), Some(address!("714b6a4ea9b94a8a7d9fd362ed72630688c8898c"))); + } + + #[test] + fn legacy_valid_pooled_decoding() { + // d3 <- payload length, d3 - c0 = 0x13 = 19 + // 0b <- nonce + // 02 <- gas_price + // 80 <- gas_limit + // 80 <- to (Create) + // 83 c5cdeb <- value + // 87 83c5acfd9e407c <- input + // 56 <- v (eip155, so modified with a chain id) + // 56 <- r + // 56 <- s + let data = &hex!("d30b02808083c5cdeb8783c5acfd9e407c565656")[..]; + + let input_rlp = &mut &data[..]; + let res = ScrollPooledTransaction::decode(input_rlp); + assert!(res.is_ok()); + assert!(input_rlp.is_empty()); + + // we can also decode_enveloped + let res = ScrollPooledTransaction::decode_2718(&mut &data[..]); + assert!(res.is_ok()); + } +}
diff --git reth/crates/scroll/alloy/consensus/src/transaction/tx_type.rs scroll-reth/crates/scroll/alloy/consensus/src/transaction/tx_type.rs new file mode 100644 index 0000000000000000000000000000000000000000..f980f3d6872db74ed351a991c6305812a5caea57 --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/transaction/tx_type.rs @@ -0,0 +1,211 @@ +//! Contains the transaction type identifier for Scroll. + +use alloy_consensus::Typed2718; +use alloy_eips::eip2718::Eip2718Error; +use alloy_primitives::{U64, U8}; +use alloy_rlp::{BufMut, Decodable, Encodable}; +use derive_more::Display; +#[cfg(feature = "reth-codec")] +use reth_codecs::{ + __private::bytes, + txtype::{ + COMPACT_EXTENDED_IDENTIFIER_FLAG, COMPACT_IDENTIFIER_EIP1559, COMPACT_IDENTIFIER_EIP2930, + COMPACT_IDENTIFIER_LEGACY, + }, + Compact, +}; + +/// Identifier for an Scroll L1 message transaction +pub const L1_MESSAGE_TX_TYPE_ID: u8 = 126; // 0x7E + +/// Scroll `TransactionType` flags as specified in <https://docs.scroll.io/en/technology/chain/transactions/>. +#[repr(u8)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Display)] +pub enum ScrollTxType { + /// Legacy transaction type. + #[display("legacy")] + Legacy = 0, + /// EIP-2930 transaction type. + #[display("eip2930")] + Eip2930 = 1, + /// EIP-1559 transaction type. + #[display("eip1559")] + Eip1559 = 2, + /// EIP-7702 transaction type. + #[display("eip7702")] + Eip7702 = 4, + /// L1 message transaction type. + #[display("l1_message")] + L1Message = L1_MESSAGE_TX_TYPE_ID, +} + +impl ScrollTxType { + /// List of all variants. + pub const ALL: [Self; 5] = + [Self::Legacy, Self::Eip1559, Self::Eip2930, Self::Eip7702, Self::L1Message]; +} + +#[cfg(any(test, feature = "arbitrary"))] +impl arbitrary::Arbitrary<'_> for ScrollTxType { + fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> { + let i = u.choose_index(Self::ALL.len())?; + Ok(Self::ALL[i]) + } +} + +impl From<ScrollTxType> for U8 { + fn from(tx_type: ScrollTxType) -> Self { + Self::from(u8::from(tx_type)) + } +} + +impl From<ScrollTxType> for u8 { + fn from(v: ScrollTxType) -> Self { + v as Self + } +} + +impl TryFrom<u8> for ScrollTxType { + type Error = Eip2718Error; + + fn try_from(value: u8) -> Result<Self, Self::Error> { + Ok(match value { + x if x == Self::Legacy as u8 => Self::Legacy, + x if x == Self::Eip2930 as u8 => Self::Eip2930, + x if x == Self::Eip1559 as u8 => Self::Eip1559, + x if x == Self::Eip7702 as u8 => Self::Eip7702, + x if x == Self::L1Message as u8 => Self::L1Message, + _ => return Err(Eip2718Error::UnexpectedType(value)), + }) + } +} + +impl TryFrom<u64> for ScrollTxType { + type Error = &'static str; + + fn try_from(value: u64) -> Result<Self, Self::Error> { + let err = || "invalid tx type"; + let value: u8 = value.try_into().map_err(|_| err())?; + Self::try_from(value).map_err(|_| err()) + } +} + +impl TryFrom<U64> for ScrollTxType { + type Error = &'static str; + + fn try_from(value: U64) -> Result<Self, Self::Error> { + value.to::<u64>().try_into() + } +} + +impl PartialEq<u8> for ScrollTxType { + fn eq(&self, other: &u8) -> bool { + (*self as u8) == *other + } +} + +impl PartialEq<ScrollTxType> for u8 { + fn eq(&self, other: &ScrollTxType) -> bool { + *self == *other as Self + } +} + +impl Encodable for ScrollTxType { + fn encode(&self, out: &mut dyn BufMut) { + (*self as u8).encode(out); + } + + fn length(&self) -> usize { + 1 + } +} + +impl Decodable for ScrollTxType { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> { + let ty = u8::decode(buf)?; + + Self::try_from(ty).map_err(|_| alloy_rlp::Error::Custom("invalid transaction type")) + } +} + +#[cfg(feature = "reth-codec")] +impl Compact for ScrollTxType { + fn to_compact<B>(&self, buf: &mut B) -> usize + where + B: BufMut + AsMut<[u8]>, + { + match self { + Self::Legacy => COMPACT_IDENTIFIER_LEGACY, + Self::Eip2930 => COMPACT_IDENTIFIER_EIP2930, + Self::Eip1559 => COMPACT_IDENTIFIER_EIP1559, + Self::Eip7702 => { + buf.put_u8(alloy_eips::eip7702::constants::EIP7702_TX_TYPE_ID); + COMPACT_EXTENDED_IDENTIFIER_FLAG + } + Self::L1Message => { + buf.put_u8(L1_MESSAGE_TX_TYPE_ID); + COMPACT_EXTENDED_IDENTIFIER_FLAG + } + } + } + + // For backwards compatibility purposes only 2 bits of the type are encoded in the identifier + // parameter. In the case of a [`COMPACT_EXTENDED_IDENTIFIER_FLAG`], the full transaction type + // is read from the buffer as a single byte. + fn from_compact(mut buf: &[u8], identifier: usize) -> (Self, &[u8]) { + use bytes::Buf; + ( + match identifier { + COMPACT_IDENTIFIER_LEGACY => Self::Legacy, + COMPACT_IDENTIFIER_EIP2930 => Self::Eip2930, + COMPACT_IDENTIFIER_EIP1559 => Self::Eip1559, + COMPACT_EXTENDED_IDENTIFIER_FLAG => { + let extended_identifier = buf.get_u8(); + match extended_identifier { + alloy_eips::eip7702::constants::EIP7702_TX_TYPE_ID => Self::Eip7702, + L1_MESSAGE_TX_TYPE_ID => Self::L1Message, + _ => panic!("Unsupported TxType identifier: {extended_identifier}"), + } + } + _ => panic!("Unknown identifier for TxType: {identifier}"), + }, + buf, + ) + } +} + +impl Typed2718 for ScrollTxType { + fn ty(&self) -> u8 { + (*self).into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + extern crate alloc; + use alloc::{vec, vec::Vec}; + + #[test] + fn test_all_tx_types() { + assert_eq!(ScrollTxType::ALL.len(), 5); + let all = vec![ + ScrollTxType::Legacy, + ScrollTxType::Eip1559, + ScrollTxType::Eip2930, + ScrollTxType::Eip7702, + ScrollTxType::L1Message, + ]; + assert_eq!(ScrollTxType::ALL.to_vec(), all); + } + + #[test] + fn tx_type_roundtrip() { + for &tx_type in &ScrollTxType::ALL { + let mut buf = Vec::new(); + tx_type.encode(&mut buf); + let decoded = ScrollTxType::decode(&mut &buf[..]).unwrap(); + assert_eq!(tx_type, decoded); + } + } +}
diff --git reth/crates/scroll/alloy/consensus/src/transaction/typed.rs scroll-reth/crates/scroll/alloy/consensus/src/transaction/typed.rs new file mode 100644 index 0000000000000000000000000000000000000000..9bd6a4cb76c8b8d0bd4c05655ef22cb21cb7aa61 --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/transaction/typed.rs @@ -0,0 +1,597 @@ +use crate::{ScrollTxEnvelope, ScrollTxType, TxL1Message}; +use alloy_consensus::{ + transaction::RlpEcdsaEncodableTx, SignableTransaction, Signed, Transaction, TxEip1559, + TxEip2930, TxEip7702, TxLegacy, Typed2718, +}; +use alloy_eips::{eip2930::AccessList, Encodable2718}; +use alloy_primitives::{bytes::BufMut, Address, Bytes, ChainId, Signature, TxHash, TxKind, B256}; +#[cfg(feature = "reth-codec")] +use { + reth_codecs::{Compact, __private::bytes}, + reth_codecs_derive::generate_tests, +}; + +/// The `TypedTransaction` enum represents all Ethereum transaction request types, modified for +/// Scroll +/// +/// Its variants correspond to specific allowed transactions: +/// 1. `Legacy` (pre-EIP2718) [`TxLegacy`] +/// 2. `EIP2930` (state access lists) [`TxEip2930`] +/// 3. `EIP1559` [`TxEip1559`] +/// 4. `Eip7702` [`TxEip7702`] +/// 5. `L1Message` [`TxL1Message`] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + serde( + from = "serde_from::MaybeTaggedTypedTransaction", + into = "serde_from::TaggedTypedTransaction" + ) +)] +pub enum ScrollTypedTransaction { + /// Legacy transaction + Legacy(TxLegacy), + /// EIP-2930 transaction + Eip2930(TxEip2930), + /// EIP-1559 transaction + Eip1559(TxEip1559), + /// EIP-7702 transaction + Eip7702(TxEip7702), + /// Scroll L1 message transaction + L1Message(TxL1Message), +} + +impl From<TxLegacy> for ScrollTypedTransaction { + fn from(tx: TxLegacy) -> Self { + Self::Legacy(tx) + } +} + +impl From<TxEip2930> for ScrollTypedTransaction { + fn from(tx: TxEip2930) -> Self { + Self::Eip2930(tx) + } +} + +impl From<TxEip1559> for ScrollTypedTransaction { + fn from(tx: TxEip1559) -> Self { + Self::Eip1559(tx) + } +} + +impl From<TxEip7702> for ScrollTypedTransaction { + fn from(tx: TxEip7702) -> Self { + Self::Eip7702(tx) + } +} + +impl From<TxL1Message> for ScrollTypedTransaction { + fn from(tx: TxL1Message) -> Self { + Self::L1Message(tx) + } +} + +impl From<ScrollTxEnvelope> for ScrollTypedTransaction { + fn from(envelope: ScrollTxEnvelope) -> Self { + match envelope { + ScrollTxEnvelope::Legacy(tx) => Self::Legacy(tx.strip_signature()), + ScrollTxEnvelope::Eip2930(tx) => Self::Eip2930(tx.strip_signature()), + ScrollTxEnvelope::Eip1559(tx) => Self::Eip1559(tx.strip_signature()), + ScrollTxEnvelope::Eip7702(tx) => Self::Eip7702(tx.strip_signature()), + ScrollTxEnvelope::L1Message(tx) => Self::L1Message(tx.into_inner()), + } + } +} + +impl ScrollTypedTransaction { + /// Return the [`ScrollTxType`] of the inner txn. + pub const fn tx_type(&self) -> ScrollTxType { + match self { + Self::Legacy(_) => ScrollTxType::Legacy, + Self::Eip2930(_) => ScrollTxType::Eip2930, + Self::Eip1559(_) => ScrollTxType::Eip1559, + Self::Eip7702(_) => ScrollTxType::Eip7702, + Self::L1Message(_) => ScrollTxType::L1Message, + } + } + + /// Return the inner legacy transaction if it exists. + pub const fn legacy(&self) -> Option<&TxLegacy> { + match self { + Self::Legacy(tx) => Some(tx), + _ => None, + } + } + + /// Return the inner EIP-2930 transaction if it exists. + pub const fn eip2930(&self) -> Option<&TxEip2930> { + match self { + Self::Eip2930(tx) => Some(tx), + _ => None, + } + } + + /// Return the inner EIP-1559 transaction if it exists. + pub const fn eip1559(&self) -> Option<&TxEip1559> { + match self { + Self::Eip1559(tx) => Some(tx), + _ => None, + } + } + + /// Return the inner EIP-1559 transaction if it exists. + pub const fn eip7702(&self) -> Option<&TxEip7702> { + match self { + Self::Eip7702(tx) => Some(tx), + _ => None, + } + } + + /// Return the inner l1 message if it exists. + pub const fn l1_message(&self) -> Option<&TxL1Message> { + match self { + Self::L1Message(tx) => Some(tx), + _ => None, + } + } + + /// Calculates the signing hash for the transaction. + pub fn signature_hash(&self) -> B256 { + match self { + Self::Legacy(tx) => tx.signature_hash(), + Self::Eip2930(tx) => tx.signature_hash(), + Self::Eip1559(tx) => tx.signature_hash(), + Self::Eip7702(tx) => tx.signature_hash(), + Self::L1Message(_) => B256::ZERO, + } + } +} + +impl Typed2718 for ScrollTypedTransaction { + fn ty(&self) -> u8 { + match self { + Self::Legacy(_) => ScrollTxType::Legacy as u8, + Self::Eip2930(_) => ScrollTxType::Eip2930 as u8, + Self::Eip1559(_) => ScrollTxType::Eip1559 as u8, + Self::Eip7702(_) => ScrollTxType::Eip7702 as u8, + Self::L1Message(_) => ScrollTxType::L1Message as u8, + } + } +} + +impl Transaction for ScrollTypedTransaction { + fn chain_id(&self) -> Option<alloy_primitives::ChainId> { + match self { + Self::Legacy(tx) => tx.chain_id(), + Self::Eip2930(tx) => tx.chain_id(), + Self::Eip1559(tx) => tx.chain_id(), + Self::Eip7702(tx) => tx.chain_id(), + Self::L1Message(tx) => tx.chain_id(), + } + } + + fn nonce(&self) -> u64 { + match self { + Self::Legacy(tx) => tx.nonce(), + Self::Eip2930(tx) => tx.nonce(), + Self::Eip1559(tx) => tx.nonce(), + Self::Eip7702(tx) => tx.nonce(), + Self::L1Message(tx) => tx.nonce(), + } + } + + fn gas_limit(&self) -> u64 { + match self { + Self::Legacy(tx) => tx.gas_limit(), + Self::Eip2930(tx) => tx.gas_limit(), + Self::Eip1559(tx) => tx.gas_limit(), + Self::Eip7702(tx) => tx.gas_limit(), + Self::L1Message(tx) => tx.gas_limit(), + } + } + + fn gas_price(&self) -> Option<u128> { + match self { + Self::Legacy(tx) => tx.gas_price(), + Self::Eip2930(tx) => tx.gas_price(), + Self::Eip1559(tx) => tx.gas_price(), + Self::Eip7702(tx) => tx.gas_price(), + Self::L1Message(tx) => tx.gas_price(), + } + } + + fn max_fee_per_gas(&self) -> u128 { + match self { + Self::Legacy(tx) => tx.max_fee_per_gas(), + Self::Eip2930(tx) => tx.max_fee_per_gas(), + Self::Eip1559(tx) => tx.max_fee_per_gas(), + Self::Eip7702(tx) => tx.max_fee_per_gas(), + Self::L1Message(tx) => tx.max_fee_per_gas(), + } + } + + fn max_priority_fee_per_gas(&self) -> Option<u128> { + match self { + Self::Legacy(tx) => tx.max_priority_fee_per_gas(), + Self::Eip2930(tx) => tx.max_priority_fee_per_gas(), + Self::Eip1559(tx) => tx.max_priority_fee_per_gas(), + Self::Eip7702(tx) => tx.max_priority_fee_per_gas(), + Self::L1Message(tx) => tx.max_priority_fee_per_gas(), + } + } + + fn max_fee_per_blob_gas(&self) -> Option<u128> { + match self { + Self::Legacy(tx) => tx.max_fee_per_blob_gas(), + Self::Eip2930(tx) => tx.max_fee_per_blob_gas(), + Self::Eip1559(tx) => tx.max_fee_per_blob_gas(), + Self::Eip7702(tx) => tx.max_fee_per_blob_gas(), + Self::L1Message(tx) => tx.max_fee_per_blob_gas(), + } + } + + fn priority_fee_or_price(&self) -> u128 { + match self { + Self::Legacy(tx) => tx.priority_fee_or_price(), + Self::Eip2930(tx) => tx.priority_fee_or_price(), + Self::Eip1559(tx) => tx.priority_fee_or_price(), + Self::Eip7702(tx) => tx.priority_fee_or_price(), + Self::L1Message(tx) => tx.priority_fee_or_price(), + } + } + + fn to(&self) -> Option<Address> { + match self { + Self::Legacy(tx) => tx.to(), + Self::Eip2930(tx) => tx.to(), + Self::Eip1559(tx) => tx.to(), + Self::Eip7702(tx) => tx.to(), + Self::L1Message(tx) => tx.to(), + } + } + + fn kind(&self) -> TxKind { + match self { + Self::Legacy(tx) => tx.kind(), + Self::Eip2930(tx) => tx.kind(), + Self::Eip1559(tx) => tx.kind(), + Self::Eip7702(tx) => tx.kind(), + Self::L1Message(tx) => tx.kind(), + } + } + + fn value(&self) -> alloy_primitives::U256 { + match self { + Self::Legacy(tx) => tx.value(), + Self::Eip2930(tx) => tx.value(), + Self::Eip1559(tx) => tx.value(), + Self::Eip7702(tx) => tx.value(), + Self::L1Message(tx) => tx.value(), + } + } + + fn input(&self) -> &Bytes { + match self { + Self::Legacy(tx) => tx.input(), + Self::Eip2930(tx) => tx.input(), + Self::Eip1559(tx) => tx.input(), + Self::Eip7702(tx) => tx.input(), + Self::L1Message(tx) => tx.input(), + } + } + + fn access_list(&self) -> Option<&AccessList> { + match self { + Self::Legacy(tx) => tx.access_list(), + Self::Eip2930(tx) => tx.access_list(), + Self::Eip1559(tx) => tx.access_list(), + Self::Eip7702(tx) => tx.access_list(), + Self::L1Message(tx) => tx.access_list(), + } + } + + fn blob_versioned_hashes(&self) -> Option<&[alloy_primitives::B256]> { + match self { + Self::Legacy(tx) => tx.blob_versioned_hashes(), + Self::Eip2930(tx) => tx.blob_versioned_hashes(), + Self::Eip1559(tx) => tx.blob_versioned_hashes(), + Self::Eip7702(tx) => tx.blob_versioned_hashes(), + Self::L1Message(tx) => tx.blob_versioned_hashes(), + } + } + + fn authorization_list(&self) -> Option<&[alloy_eips::eip7702::SignedAuthorization]> { + match self { + Self::Legacy(tx) => tx.authorization_list(), + Self::Eip2930(tx) => tx.authorization_list(), + Self::Eip1559(tx) => tx.authorization_list(), + Self::Eip7702(tx) => tx.authorization_list(), + Self::L1Message(tx) => tx.authorization_list(), + } + } + + fn is_dynamic_fee(&self) -> bool { + match self { + Self::Legacy(tx) => tx.is_dynamic_fee(), + Self::Eip2930(tx) => tx.is_dynamic_fee(), + Self::Eip1559(tx) => tx.is_dynamic_fee(), + Self::Eip7702(tx) => tx.is_dynamic_fee(), + Self::L1Message(tx) => tx.is_dynamic_fee(), + } + } + + fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 { + match self { + Self::Legacy(tx) => tx.effective_gas_price(base_fee), + Self::Eip2930(tx) => tx.effective_gas_price(base_fee), + Self::Eip1559(tx) => tx.effective_gas_price(base_fee), + Self::Eip7702(tx) => tx.effective_gas_price(base_fee), + Self::L1Message(tx) => tx.effective_gas_price(base_fee), + } + } + + fn is_create(&self) -> bool { + match self { + Self::Legacy(tx) => tx.is_create(), + Self::Eip2930(tx) => tx.is_create(), + Self::Eip1559(tx) => tx.is_create(), + Self::Eip7702(tx) => tx.is_create(), + Self::L1Message(tx) => tx.is_create(), + } + } +} + +impl RlpEcdsaEncodableTx for ScrollTypedTransaction { + fn rlp_encoded_fields_length(&self) -> usize { + match self { + Self::Legacy(tx) => tx.rlp_encoded_fields_length(), + Self::Eip2930(tx) => tx.rlp_encoded_fields_length(), + Self::Eip1559(tx) => tx.rlp_encoded_fields_length(), + Self::Eip7702(tx) => tx.rlp_encoded_fields_length(), + Self::L1Message(tx) => tx.rlp_encoded_fields_length(), + } + } + + fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) { + match self { + Self::Legacy(tx) => tx.rlp_encode_fields(out), + Self::Eip2930(tx) => tx.rlp_encode_fields(out), + Self::Eip1559(tx) => tx.rlp_encode_fields(out), + Self::Eip7702(tx) => tx.rlp_encode_fields(out), + Self::L1Message(tx) => tx.rlp_encode_fields(out), + } + } + + fn eip2718_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) { + match self { + Self::Legacy(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out), + Self::Eip2930(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out), + Self::Eip1559(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out), + Self::Eip7702(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out), + Self::L1Message(tx) => tx.encode_2718(out), + } + } + + fn eip2718_encode(&self, signature: &Signature, out: &mut dyn BufMut) { + match self { + Self::Legacy(tx) => tx.eip2718_encode(signature, out), + Self::Eip2930(tx) => tx.eip2718_encode(signature, out), + Self::Eip1559(tx) => tx.eip2718_encode(signature, out), + Self::Eip7702(tx) => tx.eip2718_encode(signature, out), + Self::L1Message(tx) => tx.encode_2718(out), + } + } + + fn network_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) { + match self { + Self::Legacy(tx) => tx.network_encode_with_type(signature, tx.ty(), out), + Self::Eip2930(tx) => tx.network_encode_with_type(signature, tx.ty(), out), + Self::Eip1559(tx) => tx.network_encode_with_type(signature, tx.ty(), out), + Self::Eip7702(tx) => tx.network_encode_with_type(signature, tx.ty(), out), + Self::L1Message(tx) => tx.network_encode(out), + } + } + + fn network_encode(&self, signature: &Signature, out: &mut dyn BufMut) { + match self { + Self::Legacy(tx) => tx.network_encode(signature, out), + Self::Eip2930(tx) => tx.network_encode(signature, out), + Self::Eip1559(tx) => tx.network_encode(signature, out), + Self::Eip7702(tx) => tx.network_encode(signature, out), + Self::L1Message(tx) => tx.network_encode(out), + } + } + + fn tx_hash_with_type(&self, signature: &Signature, _ty: u8) -> TxHash { + match self { + Self::Legacy(tx) => tx.tx_hash_with_type(signature, tx.ty()), + Self::Eip2930(tx) => tx.tx_hash_with_type(signature, tx.ty()), + Self::Eip1559(tx) => tx.tx_hash_with_type(signature, tx.ty()), + Self::Eip7702(tx) => tx.tx_hash_with_type(signature, tx.ty()), + Self::L1Message(tx) => tx.tx_hash(), + } + } + + fn tx_hash(&self, signature: &Signature) -> TxHash { + match self { + Self::Legacy(tx) => tx.tx_hash(signature), + Self::Eip2930(tx) => tx.tx_hash(signature), + Self::Eip1559(tx) => tx.tx_hash(signature), + Self::Eip7702(tx) => tx.tx_hash(signature), + Self::L1Message(tx) => tx.tx_hash(), + } + } +} + +impl SignableTransaction<Signature> for ScrollTypedTransaction { + fn set_chain_id(&mut self, chain_id: ChainId) { + match self { + Self::Legacy(tx) => tx.set_chain_id(chain_id), + Self::Eip2930(tx) => tx.set_chain_id(chain_id), + Self::Eip1559(tx) => tx.set_chain_id(chain_id), + Self::Eip7702(tx) => tx.set_chain_id(chain_id), + Self::L1Message(_) => {} + } + } + + fn encode_for_signing(&self, out: &mut dyn BufMut) { + match self { + Self::Legacy(tx) => tx.encode_for_signing(out), + Self::Eip2930(tx) => tx.encode_for_signing(out), + Self::Eip1559(tx) => tx.encode_for_signing(out), + Self::Eip7702(tx) => tx.encode_for_signing(out), + Self::L1Message(_) => {} + } + } + + fn payload_len_for_signature(&self) -> usize { + match self { + Self::Legacy(tx) => tx.payload_len_for_signature(), + Self::Eip2930(tx) => tx.payload_len_for_signature(), + Self::Eip1559(tx) => tx.payload_len_for_signature(), + Self::Eip7702(tx) => tx.payload_len_for_signature(), + Self::L1Message(_) => 0, + } + } + + fn into_signed(self, signature: Signature) -> Signed<Self, Signature> + where + Self: Sized, + { + let hash = self.tx_hash(&signature); + Signed::new_unchecked(self, signature, hash) + } +} + +#[cfg(feature = "reth-codec")] +impl Compact for ScrollTypedTransaction { + fn to_compact<B>(&self, out: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + let identifier = self.tx_type().to_compact(out); + match self { + Self::Legacy(tx) => tx.to_compact(out), + Self::Eip2930(tx) => tx.to_compact(out), + Self::Eip1559(tx) => tx.to_compact(out), + Self::Eip7702(tx) => tx.to_compact(out), + Self::L1Message(tx) => tx.to_compact(out), + }; + identifier + } + + fn from_compact(buf: &[u8], identifier: usize) -> (Self, &[u8]) { + let (tx_type, buf) = ScrollTxType::from_compact(buf, identifier); + match tx_type { + ScrollTxType::Legacy => { + let (tx, buf) = Compact::from_compact(buf, buf.len()); + (Self::Legacy(tx), buf) + } + ScrollTxType::Eip2930 => { + let (tx, buf) = Compact::from_compact(buf, buf.len()); + (Self::Eip2930(tx), buf) + } + ScrollTxType::Eip1559 => { + let (tx, buf) = Compact::from_compact(buf, buf.len()); + (Self::Eip1559(tx), buf) + } + ScrollTxType::Eip7702 => { + let (tx, buf) = Compact::from_compact(buf, buf.len()); + (Self::Eip7702(tx), buf) + } + ScrollTxType::L1Message => { + let (tx, buf) = Compact::from_compact(buf, buf.len()); + (Self::L1Message(tx), buf) + } + } + } +} + +#[cfg(feature = "reth-codec")] +generate_tests!( + #[compact] + ScrollTypedTransaction, + ScrollTypedTransactionTests +); + +#[cfg(feature = "serde")] +mod serde_from { + //! NB: Why do we need this? + //! + //! Because the tag may be missing, we need an abstraction over tagged (with + //! type) and untagged (always legacy). This is + //! [`MaybeTaggedTypedTransaction`]. + //! + //! The tagged variant is [`TaggedTypedTransaction`], which always has a + //! type tag. + //! + //! We serialize via [`TaggedTypedTransaction`] and deserialize via + //! [`MaybeTaggedTypedTransaction`]. + use super::*; + + #[derive(Debug, serde::Deserialize)] + #[serde(untagged)] + pub(crate) enum MaybeTaggedTypedTransaction { + Tagged(TaggedTypedTransaction), + Untagged(TxLegacy), + } + + #[derive(Debug, serde::Serialize, serde::Deserialize)] + #[serde(tag = "type")] + pub(crate) enum TaggedTypedTransaction { + /// `Legacy` transaction + #[serde(rename = "0x00", alias = "0x0")] + Legacy(TxLegacy), + /// `EIP-2930` transaction + #[serde(rename = "0x01", alias = "0x1")] + Eip2930(TxEip2930), + /// `EIP-1559` transaction + #[serde(rename = "0x02", alias = "0x2")] + Eip1559(TxEip1559), + /// `EIP-7702` transaction + #[serde(rename = "0x04", alias = "0x4")] + Eip7702(TxEip7702), + /// `L1Message` transaction + #[serde( + rename = "0x7e", + alias = "0x7E", + serialize_with = "crate::serde_l1_message_tx_rpc" + )] + L1Message(TxL1Message), + } + + impl From<MaybeTaggedTypedTransaction> for ScrollTypedTransaction { + fn from(value: MaybeTaggedTypedTransaction) -> Self { + match value { + MaybeTaggedTypedTransaction::Tagged(tagged) => tagged.into(), + MaybeTaggedTypedTransaction::Untagged(tx) => Self::Legacy(tx), + } + } + } + + impl From<TaggedTypedTransaction> for ScrollTypedTransaction { + fn from(value: TaggedTypedTransaction) -> Self { + match value { + TaggedTypedTransaction::Legacy(signed) => Self::Legacy(signed), + TaggedTypedTransaction::Eip2930(signed) => Self::Eip2930(signed), + TaggedTypedTransaction::Eip1559(signed) => Self::Eip1559(signed), + TaggedTypedTransaction::Eip7702(signed) => Self::Eip7702(signed), + TaggedTypedTransaction::L1Message(tx) => Self::L1Message(tx), + } + } + } + + impl From<ScrollTypedTransaction> for TaggedTypedTransaction { + fn from(value: ScrollTypedTransaction) -> Self { + match value { + ScrollTypedTransaction::Legacy(signed) => Self::Legacy(signed), + ScrollTypedTransaction::Eip2930(signed) => Self::Eip2930(signed), + ScrollTypedTransaction::Eip1559(signed) => Self::Eip1559(signed), + ScrollTypedTransaction::Eip7702(signed) => Self::Eip7702(signed), + ScrollTypedTransaction::L1Message(tx) => Self::L1Message(tx), + } + } + } +}
diff --git reth/crates/scroll/alloy/evm/Cargo.toml scroll-reth/crates/scroll/alloy/evm/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..2c61be7214848321493c951beb1b58b134643826 --- /dev/null +++ scroll-reth/crates/scroll/alloy/evm/Cargo.toml @@ -0,0 +1,72 @@ +[package] +name = "scroll-alloy-evm" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# alloy +alloy-consensus = { workspace = true, default-features = false } +alloy-eips = { workspace = true, default-features = false } +alloy-evm = { workspace = true, default-features = false } +alloy-primitives = { workspace = true, default-features = false } + +# revm +revm = { workspace = true, default-features = false, features = ["optional_no_base_fee"] } + +# scroll +revm-scroll = { workspace = true, default-features = false } + +# scroll +scroll-alloy-consensus = { workspace = true, default-features = false } +scroll-alloy-hardforks = { workspace = true, default-features = false } + +# misc +auto_impl = { workspace = true, default-features = false } +serde = { workspace = true, default-features = false, features = ["derive"], optional = true } +encoder-standard = { workspace = true, default-features = false, optional = true } + +[dev-dependencies] +alloy-hardforks.workspace = true +alloy-primitives = { workspace = true, features = ["getrandom"] } +eyre.workspace = true +reth-evm.workspace = true +reth-scroll-chainspec.workspace = true +reth-scroll-evm.workspace = true + +[features] +default = ["std"] +std = [ + "alloy-evm/std", + "alloy-primitives/std", + "revm-scroll/std", + "revm/std", + "serde/std", + "alloy-consensus/std", + "alloy-eips/std", + "scroll-alloy-consensus/std", + "scroll-alloy-hardforks/std", + "reth-evm/std", + "reth-scroll-chainspec/std", + "reth-scroll-evm/std", + "zstd_compression", +] +serde = [ + "dep:serde", + "alloy-primitives/serde", + "revm-scroll/serde", + "revm/serde", + "alloy-eips/serde", + "alloy-consensus/serde", + "scroll-alloy-consensus/serde", + "scroll-alloy-hardforks/serde", + "alloy-hardforks/serde", +] +zstd_compression = ["encoder-standard"]
diff --git reth/crates/scroll/alloy/evm/src/block/curie.rs scroll-reth/crates/scroll/alloy/evm/src/block/curie.rs new file mode 100644 index 0000000000000000000000000000000000000000..3c65e9cc04d80cc1c986800efc4d382cec2b8fb2 --- /dev/null +++ scroll-reth/crates/scroll/alloy/evm/src/block/curie.rs @@ -0,0 +1,179 @@ +//! Curie fork transition for Scroll. +//! +//! On block 7096836, Scroll performed a transition to the Curie fork state, which brought various +//! changes to the protocol: +//! 1. Fee reduction cost thanks to the use of compressed blobs on the L1. +//! 2. Modified [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) pricing +//! model along with support for EIP-1559 and EIP-2930 transactions. +//! 3. Support for `MLOAD`, `TLOAD` and `MCOPY` ([EIP-1153](https://eips.ethereum.org/EIPS/eip-1153) +//! and [EIP-5656](https://eips.ethereum.org/EIPS/eip-5656)). +//! 4. Dynamic block time. +//! +//! Compressed blobs allowed for more transactions to be stored in each blob, reducing the DA cost +//! per transactions. Accordingly, the L1 gas oracle contract's bytecode was updated in order to +//! reflect the update in the DA costs: +//! - original formula: `(l1GasUsed(txRlp) + overhead) * l1BaseFee * scalar`. +//! - updated formula: `l1BaseFee * commitScalar + len(txRlp) * l1BlobBaseFee * blobScalar`. +//! +//! More details on the Curie update: <https://scroll.io/blog/compressing-the-gas-scrolls-curie-upgrade> + +use alloc::vec; +use revm::{ + bytecode::Bytecode, + database::{states::StorageSlot, State}, + primitives::{address, bytes, Address, Bytes, U256}, + state::AccountInfo, + Database, +}; + +/// L1 gas price oracle address. +/// <https://scrollscan.com/address/0x5300000000000000000000000000000000000002> +pub const L1_GAS_PRICE_ORACLE_ADDRESS: Address = + address!("5300000000000000000000000000000000000002"); +/// Bytecode of L1 gas price oracle at Curie transition. +pub const CURIE_L1_GAS_PRICE_ORACLE_BYTECODE: Bytes = bytes!("608060405234801561000f575f80fd5b5060043610610132575f3560e01c8063715018a6116100b4578063a911d77f11610079578063a911d77f1461024c578063bede39b514610254578063de26c4a114610267578063e88a60ad1461027a578063f2fde38b1461028d578063f45e65d8146102a0575f80fd5b8063715018a6146101eb57806384189161146101f35780638da5cb5b146101fc57806393e59dc114610226578063944b247f14610239575f80fd5b80633d0f963e116100fa5780633d0f963e146101a057806349948e0e146101b3578063519b4bd3146101c65780636a5e67e5146101cf57806370465597146101d8575f80fd5b80630c18c1621461013657806313dad5be1461015257806323e524ac1461016f5780633577afc51461017857806339455d3a1461018d575b5f80fd5b61013f60025481565b6040519081526020015b60405180910390f35b60085461015f9060ff1681565b6040519015158152602001610149565b61013f60065481565b61018b6101863660046109b3565b6102a9565b005b61018b61019b3660046109ca565b61033b565b61018b6101ae3660046109ea565b610438565b61013f6101c1366004610a2b565b6104bb565b61013f60015481565b61013f60075481565b61018b6101e63660046109b3565b6104e0565b61018b61056e565b61013f60055481565b5f5461020e906001600160a01b031681565b6040516001600160a01b039091168152602001610149565b60045461020e906001600160a01b031681565b61018b6102473660046109b3565b6105a2565b61018b61062e565b61018b6102623660046109b3565b61068a565b61013f610275366004610a2b565b610747565b61018b6102883660046109b3565b610764565b61018b61029b3660046109ea565b6107f0565b61013f60035481565b5f546001600160a01b031633146102db5760405162461bcd60e51b81526004016102d290610ad6565b60405180910390fd5b621c9c388111156102ff57604051635742c80560e11b815260040160405180910390fd5b60028190556040518181527f32740b35c0ea213650f60d44366b4fb211c9033b50714e4a1d34e65d5beb9bb4906020015b60405180910390a150565b6004805460405163efc7840160e01b815233928101929092526001600160a01b03169063efc7840190602401602060405180830381865afa158015610382573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103a69190610b0d565b6103c3576040516326b3506d60e11b815260040160405180910390fd5b600182905560058190556040518281527f351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c449060200160405180910390a16040518181527f9a14bfb5d18c4c3cf14cae19c23d7cf1bcede357ea40ca1f75cd49542c71c214906020015b60405180910390a15050565b5f546001600160a01b031633146104615760405162461bcd60e51b81526004016102d290610ad6565b600480546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f22d1c35fe072d2e42c3c8f9bd4a0d34aa84a0101d020a62517b33fdb3174e5f7910161042c565b6008545f9060ff16156104d7576104d18261087b565b92915050565b6104d1826108c1565b5f546001600160a01b031633146105095760405162461bcd60e51b81526004016102d290610ad6565b610519633b9aca006103e8610b40565b81111561053957604051631e44fdeb60e11b815260040160405180910390fd5b60038190556040518181527f3336cd9708eaf2769a0f0dc0679f30e80f15dcd88d1921b5a16858e8b85c591a90602001610330565b5f546001600160a01b031633146105975760405162461bcd60e51b81526004016102d290610ad6565b6105a05f610904565b565b5f546001600160a01b031633146105cb5760405162461bcd60e51b81526004016102d290610ad6565b6105d9633b9aca0080610b40565b8111156105f95760405163874f603160e01b815260040160405180910390fd5b60068190556040518181527f2ab3f5a4ebbcbf3c24f62f5454f52f10e1a8c9dcc5acac8f19199ce881a6a10890602001610330565b5f546001600160a01b031633146106575760405162461bcd60e51b81526004016102d290610ad6565b60085460ff161561067b576040516379f9c57560e01b815260040160405180910390fd5b6008805460ff19166001179055565b6004805460405163efc7840160e01b815233928101929092526001600160a01b03169063efc7840190602401602060405180830381865afa1580156106d1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106f59190610b0d565b610712576040516326b3506d60e11b815260040160405180910390fd5b60018190556040518181527f351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c4490602001610330565b6008545f9060ff161561075b57505f919050565b6104d182610953565b5f546001600160a01b0316331461078d5760405162461bcd60e51b81526004016102d290610ad6565b61079b633b9aca0080610b40565b8111156107bb5760405163f37ec21560e01b815260040160405180910390fd5b60078190556040518181527f6b332a036d8c3ead57dcb06c87243bd7a2aed015ddf2d0528c2501dae56331aa90602001610330565b5f546001600160a01b031633146108195760405162461bcd60e51b81526004016102d290610ad6565b6001600160a01b03811661086f5760405162461bcd60e51b815260206004820152601d60248201527f6e6577206f776e657220697320746865207a65726f206164647265737300000060448201526064016102d2565b61087881610904565b50565b5f633b9aca0060055483516007546108939190610b40565b61089d9190610b40565b6001546006546108ad9190610b40565b6108b79190610b57565b6104d19190610b6a565b5f806108cc83610953565b90505f600154826108dd9190610b40565b9050633b9aca00600354826108f29190610b40565b6108fc9190610b6a565b949350505050565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80515f908190815b818110156109a45784818151811061097557610975610b89565b01602001516001600160f81b0319165f036109955760048301925061099c565b6010830192505b60010161095b565b50506002540160400192915050565b5f602082840312156109c3575f80fd5b5035919050565b5f80604083850312156109db575f80fd5b50508035926020909101359150565b5f602082840312156109fa575f80fd5b81356001600160a01b0381168114610a10575f80fd5b9392505050565b634e487b7160e01b5f52604160045260245ffd5b5f60208284031215610a3b575f80fd5b813567ffffffffffffffff80821115610a52575f80fd5b818401915084601f830112610a65575f80fd5b813581811115610a7757610a77610a17565b604051601f8201601f19908116603f01168101908382118183101715610a9f57610a9f610a17565b81604052828152876020848701011115610ab7575f80fd5b826020860160208301375f928101602001929092525095945050505050565b60208082526017908201527f63616c6c6572206973206e6f7420746865206f776e6572000000000000000000604082015260600190565b5f60208284031215610b1d575f80fd5b81518015158114610a10575f80fd5b634e487b7160e01b5f52601160045260245ffd5b80820281158282048414176104d1576104d1610b2c565b808201808211156104d1576104d1610b2c565b5f82610b8457634e487b7160e01b5f52601260045260245ffd5b500490565b634e487b7160e01b5f52603260045260245ffdfea26469706673582212200c2ac583f18be4f94ab169ae6f2ea3a708a7c0d4424746b120b177adb39e626064736f6c63430008180033"); +/// Storage update of L1 gas price oracle at Curie transition. +pub const CURIE_L1_GAS_PRICE_ORACLE_STORAGE: [(U256, U256); 4] = [ + (L1_BLOB_BASE_FEE_SLOT, INITIAL_L1_BLOB_BASE_FEE), + (COMMIT_SCALAR_SLOT, INITIAL_COMMIT_SCALAR), + (BLOB_SCALAR_SLOT, INITIAL_BLOB_SCALAR), + (IS_CURIE_SLOT, IS_CURIE), +]; + +/// L1 gas price oracle blob base fee slot. Added in the Curie fork. +pub const L1_BLOB_BASE_FEE_SLOT: U256 = U256::from_limbs([5, 0, 0, 0]); +/// L1 gas price oracle commit scalar slot. Added in the Curie fork. +pub const COMMIT_SCALAR_SLOT: U256 = U256::from_limbs([6, 0, 0, 0]); +/// L1 gas price oracle blob scalar slot. Added in the Curie fork. +pub const BLOB_SCALAR_SLOT: U256 = U256::from_limbs([7, 0, 0, 0]); +/// L1 gas price oracle "is Curie" slot. Added in the Curie fork. +pub const IS_CURIE_SLOT: U256 = U256::from_limbs([8, 0, 0, 0]); + +/// The initial blob base fee used by the oracle contract. +pub const INITIAL_L1_BLOB_BASE_FEE: U256 = U256::from_limbs([1, 0, 0, 0]); +/// The initial commit scalar used by the oracle contract. +pub const INITIAL_COMMIT_SCALAR: U256 = U256::from_limbs([230759955285, 0, 0, 0]); +/// The initial blob scalar used by the oracle contract. +pub const INITIAL_BLOB_SCALAR: U256 = U256::from_limbs([417565260, 0, 0, 0]); +/// Curie slot is set to 1 (true) after the Curie block fork. +pub const IS_CURIE: U256 = U256::from_limbs([1, 0, 0, 0]); + +/// Applies the Scroll Curie hard fork to the state: +/// - Updates the L1 oracle contract bytecode to reflect the DA cost reduction. +/// - Sets the initial blob base fee, commit and blob scalar and sets the `isCurie` slot to 1 +/// (true). +pub(super) fn apply_curie_hard_fork<DB: Database>(state: &mut State<DB>) -> Result<(), DB::Error> { + let oracle = state.load_cache_account(L1_GAS_PRICE_ORACLE_ADDRESS)?; + + // compute the code hash + let bytecode = Bytecode::new_raw(CURIE_L1_GAS_PRICE_ORACLE_BYTECODE); + let code_hash = bytecode.hash_slow(); + + // get the old oracle account info + let old_oracle_info = oracle.account_info().unwrap_or_default(); + + // init new oracle account information + let new_oracle_info = AccountInfo { code_hash, code: Some(bytecode), ..old_oracle_info }; + + // init new storage + let new_storage = CURIE_L1_GAS_PRICE_ORACLE_STORAGE + .into_iter() + .map(|(slot, present_value)| { + ( + slot, + StorageSlot { + present_value, + previous_or_original_value: oracle.storage_slot(slot).unwrap_or_default(), + }, + ) + }) + .collect(); + + // create transition for oracle new account info and storage + let transition = oracle.change(new_oracle_info, new_storage); + + // add transition + if let Some(s) = state.transition_state.as_mut() { + s.add_transitions(vec![(L1_GAS_PRICE_ORACLE_ADDRESS, transition)]) + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use revm::{ + database::{ + states::{bundle_state::BundleRetention, plain_account::PlainStorage, StorageSlot}, + EmptyDB, State, + }, + primitives::{bytes, keccak256, U256}, + state::{AccountInfo, Bytecode}, + Database, + }; + use std::str::FromStr; + + #[test] + fn test_apply_curie_fork() -> eyre::Result<()> { + // init state + let db = EmptyDB::new(); + let mut state = + State::builder().with_database(db).with_bundle_update().without_state_clear().build(); + + // oracle pre fork state + let bytecode_pre_fork = Bytecode::new_raw( bytes!("608060405234801561001057600080fd5b50600436106100cf5760003560e01c8063715018a61161008c578063bede39b511610066578063bede39b51461018d578063de26c4a1146101a0578063f2fde38b146101b3578063f45e65d8146101c657600080fd5b8063715018a6146101475780638da5cb5b1461014f57806393e59dc11461017a57600080fd5b80630c18c162146100d45780633577afc5146100f05780633d0f963e1461010557806349948e0e14610118578063519b4bd31461012b5780637046559714610134575b600080fd5b6100dd60025481565b6040519081526020015b60405180910390f35b6101036100fe366004610671565b6101cf565b005b61010361011336600461068a565b610291565b6100dd6101263660046106d0565b61031c565b6100dd60015481565b610103610142366004610671565b610361565b610103610416565b600054610162906001600160a01b031681565b6040516001600160a01b0390911681526020016100e7565b600454610162906001600160a01b031681565b61010361019b366004610671565b61044c565b6100dd6101ae3660046106d0565b610533565b6101036101c136600461068a565b610595565b6100dd60035481565b6000546001600160a01b031633146102025760405162461bcd60e51b81526004016101f990610781565b60405180910390fd5b621c9c388111156102555760405162461bcd60e51b815260206004820152601760248201527f657863656564206d6178696d756d206f7665726865616400000000000000000060448201526064016101f9565b60028190556040518181527f32740b35c0ea213650f60d44366b4fb211c9033b50714e4a1d34e65d5beb9bb4906020015b60405180910390a150565b6000546001600160a01b031633146102bb5760405162461bcd60e51b81526004016101f990610781565b600480546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f22d1c35fe072d2e42c3c8f9bd4a0d34aa84a0101d020a62517b33fdb3174e5f7910160405180910390a15050565b60008061032883610533565b905060006001548261033a91906107b8565b9050633b9aca006003548261034f91906107b8565b61035991906107e5565b949350505050565b6000546001600160a01b0316331461038b5760405162461bcd60e51b81526004016101f990610781565b61039b633b9aca006103e86107b8565b8111156103e15760405162461bcd60e51b8152602060048201526014602482015273657863656564206d6178696d756d207363616c6560601b60448201526064016101f9565b60038190556040518181527f3336cd9708eaf2769a0f0dc0679f30e80f15dcd88d1921b5a16858e8b85c591a90602001610286565b6000546001600160a01b031633146104405760405162461bcd60e51b81526004016101f990610781565b61044a6000610621565b565b6004805460405163efc7840160e01b815233928101929092526001600160a01b03169063efc7840190602401602060405180830381865afa158015610495573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104b99190610807565b6104fe5760405162461bcd60e51b81526020600482015260166024820152752737ba103bb434ba32b634b9ba32b21039b2b73232b960511b60448201526064016101f9565b60018190556040518181527f351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c4490602001610286565b80516000908190815b818110156105865784818151811061055657610556610829565b01602001516001600160f81b0319166000036105775760048301925061057e565b6010830192505b60010161053c565b50506002540160400192915050565b6000546001600160a01b031633146105bf5760405162461bcd60e51b81526004016101f990610781565b6001600160a01b0381166106155760405162461bcd60e51b815260206004820152601d60248201527f6e6577206f776e657220697320746865207a65726f206164647265737300000060448201526064016101f9565b61061e81610621565b50565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006020828403121561068357600080fd5b5035919050565b60006020828403121561069c57600080fd5b81356001600160a01b03811681146106b357600080fd5b9392505050565b634e487b7160e01b600052604160045260246000fd5b6000602082840312156106e257600080fd5b813567ffffffffffffffff808211156106fa57600080fd5b818401915084601f83011261070e57600080fd5b813581811115610720576107206106ba565b604051601f8201601f19908116603f01168101908382118183101715610748576107486106ba565b8160405282815287602084870101111561076157600080fd5b826020860160208301376000928101602001929092525095945050505050565b60208082526017908201527f63616c6c6572206973206e6f7420746865206f776e6572000000000000000000604082015260600190565b60008160001904831182151516156107e057634e487b7160e01b600052601160045260246000fd5b500290565b60008261080257634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561081957600080fd5b815180151581146106b357600080fd5b634e487b7160e01b600052603260045260246000fdfea26469706673582212205ea335809638809cf032c794fd966e2439020737b1dcc2218435cb438286efcf64736f6c63430008100033")); + let oracle_pre_fork = AccountInfo { + code_hash: bytecode_pre_fork.hash_slow(), + code: Some(bytecode_pre_fork), + ..Default::default() + }; + let oracle_storage_pre_fork = PlainStorage::from_iter([ + (U256::ZERO, U256::from_str("0x13d24a7ff6f5ec5ff0e9c40fc3b8c9c01c65437b")?), + (U256::from(1), U256::from(0x15f50e5e)), + (U256::from(2), U256::from(0x38)), + (U256::from(3), U256::from(0x3e95ba80)), + (U256::from(4), U256::from_str("0x5300000000000000000000000000000000000003")?), + ]); + state.insert_account_with_storage( + L1_GAS_PRICE_ORACLE_ADDRESS, + oracle_pre_fork.clone(), + oracle_storage_pre_fork.clone(), + ); + + // apply curie fork + apply_curie_hard_fork(&mut state)?; + + // merge transitions + state.merge_transitions(BundleRetention::Reverts); + let bundle = state.take_bundle(); + + // check oracle account info + let oracle = bundle.state.get(&L1_GAS_PRICE_ORACLE_ADDRESS).unwrap().clone(); + let code_hash = keccak256(&CURIE_L1_GAS_PRICE_ORACLE_BYTECODE); + let bytecode = Bytecode::new_raw(CURIE_L1_GAS_PRICE_ORACLE_BYTECODE); + let expected_oracle_info = + AccountInfo { code_hash, code: Some(bytecode.clone()), ..Default::default() }; + + assert_eq!(oracle.original_info.unwrap(), oracle_pre_fork); + assert_eq!(oracle.info.unwrap(), expected_oracle_info); + + // check oracle storage changeset + let mut storage = oracle.storage.into_iter().collect::<Vec<(U256, StorageSlot)>>(); + storage.sort_by(|(a, _), (b, _)| a.cmp(b)); + for (got, expected) in storage.into_iter().zip(CURIE_L1_GAS_PRICE_ORACLE_STORAGE) { + assert_eq!(got.0, expected.0); + assert_eq!(got.1, StorageSlot { present_value: expected.1, ..Default::default() }); + } + + // check oracle original storage + for (slot, value) in oracle_storage_pre_fork { + assert_eq!(state.storage(L1_GAS_PRICE_ORACLE_ADDRESS, slot)?, value) + } + + // check deployed contract + assert_eq!(bundle.contracts.get(&code_hash).unwrap(), &bytecode); + + Ok(()) + } +}
diff --git reth/crates/scroll/alloy/evm/src/block/feynman.rs scroll-reth/crates/scroll/alloy/evm/src/block/feynman.rs new file mode 100644 index 0000000000000000000000000000000000000000..c739d4a3abd75137c6d60b88e0ce2a509d8101b0 --- /dev/null +++ scroll-reth/crates/scroll/alloy/evm/src/block/feynman.rs @@ -0,0 +1,248 @@ +//! Feynman fork transition for Scroll. + +use alloc::vec; +use revm::{ + bytecode::Bytecode, + database::{states::StorageSlot, State}, + primitives::{bytes, Bytes, U256}, + state::AccountInfo, + Database, +}; + +use super::curie::L1_GAS_PRICE_ORACLE_ADDRESS; + +/// Bytecode of L1 gas price oracle at Feynman transition. +const FEYNMAN_L1_GAS_PRICE_ORACLE_BYTECODE: Bytes = bytes!("608060405234801561000f575f80fd5b50600436106101a1575f3560e01c806384189161116100f3578063c63b9e2d11610093578063e88a60ad1161006e578063e88a60ad1461032e578063f2fde38b14610341578063f45e65d814610354578063fe5b04151461035d575f80fd5b8063c63b9e2d146102ff578063c91e514914610312578063de26c4a11461031b575f80fd5b8063944b247f116100ce578063944b247f146102be578063a911d77f146102d1578063aa5e9334146102d9578063bede39b5146102ec575f80fd5b806384189161146102785780638da5cb5b1461028157806393e59dc1146102ab575f80fd5b80633d0f963e1161015e5780636112d6db116101395780636112d6db1461024b5780636a5e67e514610254578063704655971461025d578063715018a614610270575f80fd5b80633d0f963e1461021c57806349948e0e1461022f578063519b4bd314610242575f80fd5b80630c18c162146101a557806313dad5be146101c157806323e524ac146101de5780633577afc5146101e757806339455d3a146101fc5780633b7656bb1461020f575b5f80fd5b6101ae60025481565b6040519081526020015b60405180910390f35b6008546101ce9060ff1681565b60405190151581526020016101b8565b6101ae60065481565b6101fa6101f5366004610c73565b610365565b005b6101fa61020a366004610c8a565b6103f7565b600b546101ce9060ff1681565b6101fa61022a366004610caa565b6104f4565b6101ae61023d366004610ceb565b610577565b6101ae60015481565b6101ae600a5481565b6101ae60075481565b6101fa61026b366004610c73565b6105b0565b6101fa61063e565b6101ae60055481565b5f54610293906001600160a01b031681565b6040516001600160a01b0390911681526020016101b8565b600454610293906001600160a01b031681565b6101fa6102cc366004610c73565b610672565b6101fa6106fe565b6101fa6102e7366004610c73565b61075a565b6101fa6102fa366004610c73565b6107f4565b6101fa61030d366004610c73565b6108b1565b6101ae60095481565b6101ae610329366004610ceb565b61094a565b6101fa61033c366004610c73565b610974565b6101fa61034f366004610caa565b610a00565b6101ae60035481565b6101fa610a8b565b5f546001600160a01b031633146103975760405162461bcd60e51b815260040161038e90610d96565b60405180910390fd5b621c9c388111156103bb57604051635742c80560e11b815260040160405180910390fd5b60028190556040518181527f32740b35c0ea213650f60d44366b4fb211c9033b50714e4a1d34e65d5beb9bb4906020015b60405180910390a150565b6004805460405163efc7840160e01b815233928101929092526001600160a01b03169063efc7840190602401602060405180830381865afa15801561043e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104629190610dcd565b61047f576040516326b3506d60e11b815260040160405180910390fd5b600182905560058190556040518281527f351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c449060200160405180910390a16040518181527f9a14bfb5d18c4c3cf14cae19c23d7cf1bcede357ea40ca1f75cd49542c71c214906020015b60405180910390a15050565b5f546001600160a01b0316331461051d5760405162461bcd60e51b815260040161038e90610d96565b600480546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f22d1c35fe072d2e42c3c8f9bd4a0d34aa84a0101d020a62517b33fdb3174e5f791016104e8565b600b545f9060ff16156105935761058d82610ae7565b92915050565b60085460ff16156105a75761058d82610b45565b61058d82610b81565b5f546001600160a01b031633146105d95760405162461bcd60e51b815260040161038e90610d96565b6105e9633b9aca006103e8610e00565b81111561060957604051631e44fdeb60e11b815260040160405180910390fd5b60038190556040518181527f3336cd9708eaf2769a0f0dc0679f30e80f15dcd88d1921b5a16858e8b85c591a906020016103ec565b5f546001600160a01b031633146106675760405162461bcd60e51b815260040161038e90610d96565b6106705f610bc4565b565b5f546001600160a01b0316331461069b5760405162461bcd60e51b815260040161038e90610d96565b6106a9633b9aca0080610e00565b8111156106c95760405163874f603160e01b815260040160405180910390fd5b60068190556040518181527f2ab3f5a4ebbcbf3c24f62f5454f52f10e1a8c9dcc5acac8f19199ce881a6a108906020016103ec565b5f546001600160a01b031633146107275760405162461bcd60e51b815260040161038e90610d96565b60085460ff161561074b576040516379f9c57560e01b815260040160405180910390fd5b6008805460ff19166001179055565b5f546001600160a01b031633146107835760405162461bcd60e51b815260040161038e90610d96565b633b9aca008110806107a1575061079e633b9aca0080610e00565b81115b156107bf5760405163d9b5dcdf60e01b815260040160405180910390fd5b60098190556040518181527fd50d3079c77df569cd58d55d4e5614bfe7066449009425d22bde8e75242f50bb906020016103ec565b6004805460405163efc7840160e01b815233928101929092526001600160a01b03169063efc7840190602401602060405180830381865afa15801561083b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061085f9190610dcd565b61087c576040516326b3506d60e11b815260040160405180910390fd5b60018190556040518181527f351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c44906020016103ec565b5f546001600160a01b031633146108da5760405162461bcd60e51b815260040161038e90610d96565b633b9aca008110806108f857506108f5633b9aca0080610e00565b81115b156109155760405162ae184360e01b815260040160405180910390fd5b600a8190556040518181527f8647cebb7e57360673a28415c0bed2f68c42a86c5035f1c9b2eda2b09509288a906020016103ec565b600b545f9060ff168061095f575060085460ff165b1561096b57505f919050565b61058d82610c13565b5f546001600160a01b0316331461099d5760405162461bcd60e51b815260040161038e90610d96565b6109ab633b9aca0080610e00565b8111156109cb5760405163f37ec21560e01b815260040160405180910390fd5b60078190556040518181527f6b332a036d8c3ead57dcb06c87243bd7a2aed015ddf2d0528c2501dae56331aa906020016103ec565b5f546001600160a01b03163314610a295760405162461bcd60e51b815260040161038e90610d96565b6001600160a01b038116610a7f5760405162461bcd60e51b815260206004820152601d60248201527f6e6577206f776e657220697320746865207a65726f2061646472657373000000604482015260640161038e565b610a8881610bc4565b50565b5f546001600160a01b03163314610ab45760405162461bcd60e51b815260040161038e90610d96565b600b5460ff1615610ad857604051631a7c228b60e21b815260040160405180910390fd5b600b805460ff19166001179055565b5f633b9aca0080600a548451600554600754610b039190610e00565b600154600654610b139190610e00565b610b1d9190610e17565b610b279190610e00565b610b319190610e00565b610b3b9190610e2a565b61058d9190610e2a565b5f633b9aca006005548351600754610b5d9190610e00565b610b679190610e00565b600154600654610b779190610e00565b610b3b9190610e17565b5f80610b8c83610c13565b90505f60015482610b9d9190610e00565b9050633b9aca0060035482610bb29190610e00565b610bbc9190610e2a565b949350505050565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80515f908190815b81811015610c6457848181518110610c3557610c35610e49565b01602001516001600160f81b0319165f03610c5557600483019250610c5c565b6010830192505b600101610c1b565b50506002540160400192915050565b5f60208284031215610c83575f80fd5b5035919050565b5f8060408385031215610c9b575f80fd5b50508035926020909101359150565b5f60208284031215610cba575f80fd5b81356001600160a01b0381168114610cd0575f80fd5b9392505050565b634e487b7160e01b5f52604160045260245ffd5b5f60208284031215610cfb575f80fd5b813567ffffffffffffffff80821115610d12575f80fd5b818401915084601f830112610d25575f80fd5b813581811115610d3757610d37610cd7565b604051601f8201601f19908116603f01168101908382118183101715610d5f57610d5f610cd7565b81604052828152876020848701011115610d77575f80fd5b826020860160208301375f928101602001929092525095945050505050565b60208082526017908201527f63616c6c6572206973206e6f7420746865206f776e6572000000000000000000604082015260600190565b5f60208284031215610ddd575f80fd5b81518015158114610cd0575f80fd5b634e487b7160e01b5f52601160045260245ffd5b808202811582820484141761058d5761058d610dec565b8082018082111561058d5761058d610dec565b5f82610e4457634e487b7160e01b5f52601260045260245ffd5b500490565b634e487b7160e01b5f52603260045260245ffdfea164736f6c6343000818000a"); + +/// L1 gas price oracle compression penalty threshold slot. Added in the Feynman fork. +pub const PENALTY_THRESHOLD_SLOT: U256 = U256::from_limbs([9, 0, 0, 0]); +/// L1 gas price oracle compression penalty factor slot. Added in the Feynman fork. +pub const PENALTY_FACTOR_SLOT: U256 = U256::from_limbs([10, 0, 0, 0]); +/// L1 gas price oracle "is Feynman" slot. Added in the Feynman fork. +pub const IS_FEYNMAN_SLOT: U256 = U256::from_limbs([11, 0, 0, 0]); + +/// The initial compression penalty threshold used by the oracle contract. +const INITIAL_PENALTY_THRESHOLD: U256 = U256::from_limbs([1_000_000_000, 0, 0, 0]); +/// The initial compression penalty factor used by the oracle contract. +const INITIAL_PENALTY_FACTOR: U256 = U256::from_limbs([1_000_000_000, 0, 0, 0]); +/// Feynman slot is set to 1 (true) after the Feynman block fork. +const IS_FEYNMAN: U256 = U256::from_limbs([1, 0, 0, 0]); + +/// Storage update of L1 gas price oracle at Feynman transition. +const FEYNMAN_L1_GAS_PRICE_ORACLE_STORAGE: [(U256, U256); 3] = [ + (PENALTY_THRESHOLD_SLOT, INITIAL_PENALTY_THRESHOLD), + (PENALTY_FACTOR_SLOT, INITIAL_PENALTY_FACTOR), + (IS_FEYNMAN_SLOT, IS_FEYNMAN), +]; + +/// Applies the Scroll Feynman hard fork to the state: +/// - Updates the L1 oracle contract bytecode to reflect the DA cost reduction. +/// - Sets the initial compression penalty threshold and penalty factor values. +/// - Sets the `isFeynman` slot to 1 (true). +pub(super) fn apply_feynman_hard_fork<DB: Database>( + state: &mut State<DB>, +) -> Result<(), DB::Error> { + // No-op if already applied. + // Note: This requires a storage read for every Feynman block, and it means this + // read needs to be included in the execution witness. Unfortunately, there is no + // other reliable way to apply the change only at the transition block, since + // `ScrollBlockExecutor` does not have access to the parent timestamp. + if state.storage(L1_GAS_PRICE_ORACLE_ADDRESS, IS_FEYNMAN_SLOT)? == IS_FEYNMAN { + return Ok(()) + } + + let oracle = state.load_cache_account(L1_GAS_PRICE_ORACLE_ADDRESS)?; + + // compute the code hash + let bytecode = Bytecode::new_raw(FEYNMAN_L1_GAS_PRICE_ORACLE_BYTECODE); + let code_hash = bytecode.hash_slow(); + + // get the old oracle account info + let old_oracle_info = oracle.account_info().unwrap_or_default(); + + // init new oracle account information + let new_oracle_info = AccountInfo { code_hash, code: Some(bytecode), ..old_oracle_info }; + + // init new storage + let new_storage = FEYNMAN_L1_GAS_PRICE_ORACLE_STORAGE + .into_iter() + .map(|(slot, present_value)| { + ( + slot, + StorageSlot { + present_value, + previous_or_original_value: oracle.storage_slot(slot).unwrap_or_default(), + }, + ) + }) + .collect(); + + // create transition for oracle new account info and storage + let transition = oracle.change(new_oracle_info, new_storage); + + // add transition + if let Some(s) = state.transition_state.as_mut() { + s.add_transitions(vec![(L1_GAS_PRICE_ORACLE_ADDRESS, transition)]) + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use revm::{ + database::{ + states::{bundle_state::BundleRetention, plain_account::PlainStorage, StorageSlot}, + CacheDB, EmptyDB, State, + }, + primitives::{keccak256, U256}, + state::{AccountInfo, Bytecode}, + Database, + }; + use std::str::FromStr; + + use super::super::curie::CURIE_L1_GAS_PRICE_ORACLE_BYTECODE; + + #[test] + fn test_apply_feynman_fork() -> eyre::Result<()> { + // init state + let db = EmptyDB::new(); + let mut state = + State::builder().with_database(db).with_bundle_update().without_state_clear().build(); + + // oracle pre fork state + let bytecode_pre_fork = Bytecode::new_raw(CURIE_L1_GAS_PRICE_ORACLE_BYTECODE); + let oracle_pre_fork = AccountInfo { + code_hash: bytecode_pre_fork.hash_slow(), + code: Some(bytecode_pre_fork), + ..Default::default() + }; + let oracle_storage_pre_fork = PlainStorage::from_iter([ + // owner + (U256::ZERO, U256::from_str("0x13d24a7ff6f5ec5ff0e9c40fc3b8c9c01c65437b")?), + // l1BaseFee + (U256::from(1), U256::from(0x15f50e5e)), + // overhead + (U256::from(2), U256::from(0x38)), + // scalar + (U256::from(3), U256::from(0x3e95ba80)), + // whitelist + (U256::from(4), U256::from_str("0x5300000000000000000000000000000000000003")?), + // l1BlobBaseFee + (U256::from(5), U256::from(0x15f50e5e)), + // commitScalar + (U256::from(6), U256::from(0x3e95ba80)), + // blobScalar + (U256::from(7), U256::from(0x3e95ba80)), + // isCurie + (U256::from(8), U256::from(1)), + ]); + state.insert_account_with_storage( + L1_GAS_PRICE_ORACLE_ADDRESS, + oracle_pre_fork.clone(), + oracle_storage_pre_fork.clone(), + ); + + // apply feynman fork + apply_feynman_hard_fork(&mut state)?; + + // merge transitions + state.merge_transitions(BundleRetention::Reverts); + let bundle = state.take_bundle(); + + // check oracle account info + let oracle = bundle.state.get(&L1_GAS_PRICE_ORACLE_ADDRESS).unwrap().clone(); + let code_hash = keccak256(&FEYNMAN_L1_GAS_PRICE_ORACLE_BYTECODE); + let bytecode = Bytecode::new_raw(FEYNMAN_L1_GAS_PRICE_ORACLE_BYTECODE); + let expected_oracle_info = + AccountInfo { code_hash, code: Some(bytecode.clone()), ..Default::default() }; + + assert_eq!(oracle.original_info.unwrap(), oracle_pre_fork); + assert_eq!(oracle.info.unwrap(), expected_oracle_info); + + // check oracle storage changeset + let mut storage = oracle.storage.into_iter().collect::<Vec<(U256, StorageSlot)>>(); + storage.sort_by(|(a, _), (b, _)| a.cmp(b)); + for (got, expected) in storage.into_iter().zip(FEYNMAN_L1_GAS_PRICE_ORACLE_STORAGE) { + assert_eq!(got.0, expected.0); + assert_eq!(got.1, StorageSlot { present_value: expected.1, ..Default::default() }); + } + + // check oracle original storage + for (slot, value) in oracle_storage_pre_fork { + assert_eq!(state.storage(L1_GAS_PRICE_ORACLE_ADDRESS, slot)?, value) + } + + // check deployed contract + assert_eq!(bundle.contracts.get(&code_hash).unwrap(), &bytecode); + + Ok(()) + } + + #[test] + fn test_apply_feynman_fork_only_once() -> eyre::Result<()> { + let bytecode = Bytecode::new_raw(FEYNMAN_L1_GAS_PRICE_ORACLE_BYTECODE); + + let oracle_account = AccountInfo { + code_hash: bytecode.hash_slow(), + code: Some(bytecode), + ..Default::default() + }; + + let oracle_storage = PlainStorage::from_iter([ + // owner + (U256::ZERO, U256::from_str("0x13d24a7ff6f5ec5ff0e9c40fc3b8c9c01c65437b")?), + // l1BaseFee + (U256::from(1), U256::from(0x15f50e5e)), + // overhead + (U256::from(2), U256::from(0x38)), + // scalar + (U256::from(3), U256::from(0x3e95ba80)), + // whitelist + (U256::from(4), U256::from_str("0x5300000000000000000000000000000000000003")?), + // l1BlobBaseFee + (U256::from(5), U256::from(0x15f50e5e)), + // commitScalar + (U256::from(6), U256::from(0x3e95ba80)), + // blobScalar + (U256::from(7), U256::from(0x3e95ba80)), + // isCurie + (U256::from(8), U256::from(1)), + // penaltyThreshold + (U256::from(9), U256::from(1_100_000_000u64)), + // penaltyFactor + (U256::from(10), U256::from(3_000_000_000u64)), + // isFeynman + (U256::from(11), U256::from(1)), + ]); + + // init state, + // we write to db directly to make sure we do not have account storage in cache + let mut db = CacheDB::new(EmptyDB::default()); + + db.insert_account_info(L1_GAS_PRICE_ORACLE_ADDRESS, oracle_account); + + for (slot, value) in oracle_storage { + db.insert_account_storage(L1_GAS_PRICE_ORACLE_ADDRESS, slot, value).unwrap(); + } + + let mut state = + State::builder().with_database(db).with_bundle_update().without_state_clear().build(); + + // make sure account is in cache + state.load_cache_account(L1_GAS_PRICE_ORACLE_ADDRESS)?; + + // apply feynman fork + apply_feynman_hard_fork(&mut state)?; + + // merge transitions + state.merge_transitions(BundleRetention::Reverts); + let bundle = state.take_bundle(); + + // isFeynman is already set, apply_feynman_hard_fork should be a no-op + assert_eq!(bundle.state.get(&L1_GAS_PRICE_ORACLE_ADDRESS), None); + + Ok(()) + } +}
diff --git reth/crates/scroll/alloy/evm/src/block/mod.rs scroll-reth/crates/scroll/alloy/evm/src/block/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..0c53e5f7ce37806fde97358ad11f948ee471c5ef --- /dev/null +++ scroll-reth/crates/scroll/alloy/evm/src/block/mod.rs @@ -0,0 +1,405 @@ +pub mod curie; +pub mod feynman; + +pub use receipt_builder::{ReceiptBuilderCtx, ScrollReceiptBuilder}; +mod receipt_builder; + +use crate::{ + block::{ + curie::{apply_curie_hard_fork, L1_GAS_PRICE_ORACLE_ADDRESS}, + feynman::apply_feynman_hard_fork, + }, + system_caller::ScrollSystemCaller, + FromTxWithCompressionRatio, ScrollDefaultPrecompilesFactory, ScrollEvm, ScrollEvmFactory, + ScrollPrecompilesFactory, ScrollTransactionIntoTxEnv, ToTxWithCompressionRatio, +}; +use alloc::{boxed::Box, format, vec::Vec}; + +use alloy_consensus::{Transaction, TxReceipt, Typed2718}; +use alloy_eips::Encodable2718; +use alloy_evm::{ + block::{ + BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockExecutorFactory, + BlockExecutorFor, BlockValidationError, CommitChanges, ExecutableTx, OnStateHook, + }, + Database, Evm, EvmFactory, FromRecoveredTx, FromTxWithEncoded, +}; +use alloy_primitives::{B256, U256}; +use revm::{ + context::{ + result::{ExecutionResult, InvalidTransaction, ResultAndState}, + TxEnv, + }, + database::State, + handler::PrecompileProvider, + interpreter::InterpreterResult, + DatabaseCommit, Inspector, +}; +use revm_scroll::builder::ScrollContext; +use scroll_alloy_consensus::L1_MESSAGE_TRANSACTION_TYPE; +use scroll_alloy_hardforks::{ScrollHardfork, ScrollHardforks}; + +/// A cache for transaction compression ratios. +pub type ScrollTxCompressionRatios = Vec<U256>; + +/// Context for Scroll Block Execution. +#[derive(Debug, Default, Clone)] +pub struct ScrollBlockExecutionCtx { + /// Parent block hash. + pub parent_hash: B256, +} + +/// Block executor for Scroll. +#[derive(Debug)] +pub struct ScrollBlockExecutor<Evm, R: ScrollReceiptBuilder, Spec> { + /// Spec. + spec: Spec, + /// Receipt builder. + receipt_builder: R, + /// The EVM used by executor. + evm: Evm, + /// Context for block execution. + ctx: ScrollBlockExecutionCtx, + /// Receipts of executed transactions. + receipts: Vec<R::Receipt>, + /// Total gas used by executed transactions. + gas_used: u64, + /// Utility to call system smart contracts. + system_caller: ScrollSystemCaller<Spec>, +} + +impl<E, R: ScrollReceiptBuilder, Spec> ScrollBlockExecutor<E, R, Spec> { + /// Returns the spec for [`ScrollBlockExecutor`]. + pub const fn spec(&self) -> &Spec { + &self.spec + } +} + +impl<E, R, Spec> ScrollBlockExecutor<E, R, Spec> +where + E: EvmExt, + R: ScrollReceiptBuilder, + Spec: ScrollHardforks + Clone, +{ + /// Creates a new [`ScrollBlockExecutor`]. + pub fn new(evm: E, ctx: ScrollBlockExecutionCtx, spec: Spec, receipt_builder: R) -> Self { + Self { + evm, + ctx, + system_caller: ScrollSystemCaller::new(spec.clone()), + spec, + receipt_builder, + receipts: Vec::new(), + gas_used: 0, + } + } +} + +impl<'db, DB, E, R, Spec> ScrollBlockExecutor<E, R, Spec> +where + DB: Database + 'db, + E: EvmExt< + DB = &'db mut State<DB>, + Tx: FromRecoveredTx<R::Transaction> + + FromTxWithEncoded<R::Transaction> + + FromTxWithCompressionRatio<R::Transaction>, + >, + R: ScrollReceiptBuilder<Transaction: Transaction + Encodable2718, Receipt: TxReceipt>, + Spec: ScrollHardforks, +{ + /// Executes all transactions in a block, applying pre and post execution changes. The provided + /// transaction compression ratios are expected to be in the same order as the + /// transactions. + pub fn execute_block_with_compression_cache( + mut self, + transactions: impl IntoIterator< + Item = impl ExecutableTx<Self> + + ToTxWithCompressionRatio<<Self as BlockExecutor>::Transaction>, + >, + compression_ratios: impl IntoIterator<Item = U256>, + ) -> Result<BlockExecutionResult<R::Receipt>, BlockExecutionError> + where + Self: Sized, + { + self.apply_pre_execution_changes()?; + + for (tx, compression_ratio) in transactions.into_iter().zip(compression_ratios.into_iter()) + { + let tx = tx.with_compression_ratio(compression_ratio); + self.execute_transaction(&tx)?; + } + + self.apply_post_execution_changes() + } +} + +impl<'db, DB, E, R, Spec> BlockExecutor for ScrollBlockExecutor<E, R, Spec> +where + DB: Database + 'db, + E: EvmExt< + DB = &'db mut State<DB>, + Tx: FromRecoveredTx<R::Transaction> + FromTxWithEncoded<R::Transaction>, + >, + R: ScrollReceiptBuilder<Transaction: Transaction + Encodable2718, Receipt: TxReceipt>, + Spec: ScrollHardforks, +{ + type Transaction = R::Transaction; + type Receipt = R::Receipt; + type Evm = E; + + fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { + // set state clear flag if the block is after the Spurious Dragon hardfork. + let state_clear_flag = + self.spec.is_spurious_dragon_active_at_block(self.evm.block().number.to()); + self.evm.db_mut().set_state_clear_flag(state_clear_flag); + + // load the l1 gas oracle contract in cache. + let _ = self + .evm + .db_mut() + .load_cache_account(L1_GAS_PRICE_ORACLE_ADDRESS) + .map_err(BlockExecutionError::other)?; + + // apply gas oracle predeploy upgrade at Curie transition block. + if self + .spec + .scroll_fork_activation(ScrollHardfork::Curie) + .transitions_at_block(self.evm.block().number.to()) + { + if let Err(err) = apply_curie_hard_fork(self.evm.db_mut()) { + return Err(BlockExecutionError::msg(format!( + "error occurred at Curie fork: {err:?}" + ))); + }; + } + + // apply gas oracle predeploy upgrade at Feynman transition block. + if self + .spec + .scroll_fork_activation(ScrollHardfork::Feynman) + .active_at_timestamp(self.evm.block().timestamp.to()) + { + if let Err(err) = apply_feynman_hard_fork(self.evm.db_mut()) { + return Err(BlockExecutionError::msg(format!( + "error occurred at Feynman fork: {err:?}" + ))); + }; + } + + // apply eip-2935. + self.system_caller.apply_blockhashes_contract_call(self.ctx.parent_hash, &mut self.evm)?; + + Ok(()) + } + + fn execute_transaction_with_commit_condition( + &mut self, + tx: impl ExecutableTx<Self>, + f: impl FnOnce(&ExecutionResult<<Self::Evm as Evm>::HaltReason>) -> CommitChanges, + ) -> Result<Option<u64>, BlockExecutionError> { + let chain_spec = &self.spec; + let is_l1_message = tx.tx().ty() == L1_MESSAGE_TRANSACTION_TYPE; + // The sum of the transaction’s gas limit and the gas utilized in this block prior, + // must be no greater than the block’s gasLimit. + let block_available_gas = self.evm.block().gas_limit - self.gas_used; + if tx.tx().gas_limit() > block_available_gas { + return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { + transaction_gas_limit: tx.tx().gas_limit(), + block_available_gas, + } + .into()) + } + + let hash = tx.tx().trie_hash(); + + let block = self.evm.block(); + // verify the transaction type is accepted by the current fork. + if tx.tx().is_eip2930() && !chain_spec.is_curie_active_at_block(block.number.to()) { + return Err(BlockValidationError::InvalidTx { + hash, + error: Box::new(InvalidTransaction::Eip2930NotSupported), + } + .into()) + } + if tx.tx().is_eip1559() && !chain_spec.is_curie_active_at_block(block.number.to()) { + return Err(BlockValidationError::InvalidTx { + hash, + error: Box::new(InvalidTransaction::Eip1559NotSupported), + } + .into()) + } + if tx.tx().is_eip4844() { + return Err(BlockValidationError::InvalidTx { + hash, + error: Box::new(InvalidTransaction::Eip4844NotSupported), + } + .into()) + } + if tx.tx().is_eip7702() && + !chain_spec.is_euclid_v2_active_at_timestamp(block.timestamp.to()) + { + return Err(BlockValidationError::InvalidTx { + hash, + error: Box::new(InvalidTransaction::Eip7702NotSupported), + } + .into()) + } + + // disable the base fee and nonce checks for l1 messages. + self.evm.with_base_fee_check(!is_l1_message); + self.evm.with_nonce_check(!is_l1_message); + + // execute the transaction and commit the result to the database + let ResultAndState { result, state } = + self.evm.transact(&tx).map_err(move |err| BlockExecutionError::evm(err, hash))?; + + if !f(&result).should_commit() { + return Ok(None) + }; + + let l1_fee = if is_l1_message { + U256::ZERO + } else { + // compute l1 fee for all non-l1 transaction + self.evm.l1_fee().expect("l1 fee loaded") + }; + + let gas_used = result.gas_used(); + self.gas_used += gas_used; + + let ctx = ReceiptBuilderCtx::<'_, Self::Transaction, E> { + tx: tx.tx(), + result, + cumulative_gas_used: self.gas_used, + l1_fee, + }; + self.receipts.push(self.receipt_builder.build_receipt(ctx)); + + self.evm.db_mut().commit(state); + + Ok(Some(gas_used)) + } + + fn finish(self) -> Result<(Self::Evm, BlockExecutionResult<R::Receipt>), BlockExecutionError> { + Ok(( + self.evm, + BlockExecutionResult { + receipts: self.receipts, + requests: Default::default(), + gas_used: self.gas_used, + }, + )) + } + + fn set_state_hook(&mut self, _hook: Option<Box<dyn OnStateHook>>) {} + + fn evm_mut(&mut self) -> &mut Self::Evm { + &mut self.evm + } + + fn evm(&self) -> &Self::Evm { + &self.evm + } +} + +/// An extension of the [`Evm`] trait for Scroll. +pub trait EvmExt: Evm { + /// Sets whether the evm should enable or disable the base fee checks. + fn with_base_fee_check(&mut self, enabled: bool); + /// Sets whether the evm should enable or disable the nonce checks. + fn with_nonce_check(&mut self, enabled: bool); + /// Returns the l1 fee for the transaction. + fn l1_fee(&self) -> Option<U256>; +} + +impl<DB, I, P> EvmExt for ScrollEvm<DB, I, P> +where + DB: Database, + I: Inspector<ScrollContext<DB>>, + P: PrecompileProvider<ScrollContext<DB>, Output = InterpreterResult>, +{ + fn with_base_fee_check(&mut self, enabled: bool) { + self.ctx_mut().cfg.disable_base_fee = !enabled; + } + + fn with_nonce_check(&mut self, enabled: bool) { + self.ctx_mut().cfg.disable_nonce_check = !enabled; + } + + fn l1_fee(&self) -> Option<U256> { + let l1_block_info = &self.ctx().chain; + let transaction_rlp_bytes = self.ctx().tx.rlp_bytes.as_ref()?; + let compression_ratio = self.ctx().tx.compression_ratio; + Some(l1_block_info.calculate_tx_l1_cost( + transaction_rlp_bytes, + self.ctx().cfg.spec, + compression_ratio, + )) + } +} + +/// Scroll block executor factory. +#[derive(Debug, Clone, Default, Copy)] +pub struct ScrollBlockExecutorFactory<R, Spec = ScrollHardfork, P = ScrollDefaultPrecompilesFactory> +{ + /// Receipt builder. + receipt_builder: R, + /// Chain specification. + spec: Spec, + /// EVM factory. + evm_factory: ScrollEvmFactory<P>, +} + +impl<R, Spec, P> ScrollBlockExecutorFactory<R, Spec, P> { + /// Creates a new [`ScrollBlockExecutorFactory`] with the given receipt builder, spec and + /// factory. + pub const fn new(receipt_builder: R, spec: Spec, evm_factory: ScrollEvmFactory<P>) -> Self { + Self { receipt_builder, spec, evm_factory } + } + + /// Exposes the receipt builder. + pub const fn receipt_builder(&self) -> &R { + &self.receipt_builder + } + + /// Exposes the chain specification. + pub const fn spec(&self) -> &Spec { + &self.spec + } + + /// Exposes the EVM factory. + pub const fn evm_factory(&self) -> &ScrollEvmFactory<P> { + &self.evm_factory + } +} + +impl<R, Spec, P> BlockExecutorFactory for ScrollBlockExecutorFactory<R, Spec, P> +where + R: ScrollReceiptBuilder<Transaction: Transaction + Encodable2718, Receipt: TxReceipt>, + Spec: ScrollHardforks, + P: ScrollPrecompilesFactory, + ScrollTransactionIntoTxEnv<TxEnv>: + FromRecoveredTx<R::Transaction> + FromTxWithEncoded<R::Transaction>, + Self: 'static, +{ + type EvmFactory = ScrollEvmFactory<P>; + type ExecutionCtx<'a> = ScrollBlockExecutionCtx; + type Transaction = R::Transaction; + type Receipt = R::Receipt; + + fn evm_factory(&self) -> &Self::EvmFactory { + &self.evm_factory + } + + fn create_executor<'a, DB, I>( + &'a self, + evm: <Self::EvmFactory as EvmFactory>::Evm<&'a mut State<DB>, I>, + ctx: Self::ExecutionCtx<'a>, + ) -> impl BlockExecutorFor<'a, Self, DB, I> + where + DB: Database + 'a, + I: Inspector<<Self::EvmFactory as EvmFactory>::Context<&'a mut State<DB>>> + 'a, + { + ScrollBlockExecutor::new(evm, ctx, &self.spec, &self.receipt_builder) + } +}
diff --git reth/crates/scroll/alloy/evm/src/block/receipt_builder.rs scroll-reth/crates/scroll/alloy/evm/src/block/receipt_builder.rs new file mode 100644 index 0000000000000000000000000000000000000000..eab12b25ba75b95c9f7cbcaeff5f1119cebeacee --- /dev/null +++ scroll-reth/crates/scroll/alloy/evm/src/block/receipt_builder.rs @@ -0,0 +1,32 @@ +use alloy_evm::Evm; +use alloy_primitives::U256; +use core::fmt::Debug; +use revm::context::result::ExecutionResult; + +/// Context for building a receipt. +#[derive(Debug)] +pub struct ReceiptBuilderCtx<'a, T, E: Evm> { + /// Transaction + pub tx: &'a T, + /// Result of transaction execution. + pub result: ExecutionResult<E::HaltReason>, + /// Cumulative gas used. + pub cumulative_gas_used: u64, + /// L1 fee. + pub l1_fee: U256, +} + +/// Type that knows how to build a receipt based on execution result. +#[auto_impl::auto_impl(&, Arc)] +pub trait ScrollReceiptBuilder: Debug { + /// Transaction type. + type Transaction; + /// Receipt type. + type Receipt; + + /// Builds a receipt given a transaction and the result of the execution. + fn build_receipt<'a, E: Evm>( + &self, + ctx: ReceiptBuilderCtx<'a, Self::Transaction, E>, + ) -> Self::Receipt; +}
diff --git reth/crates/scroll/alloy/evm/src/lib.rs scroll-reth/crates/scroll/alloy/evm/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..5ec8dc38009f1e81d562653472dfd018d95de772 --- /dev/null +++ scroll-reth/crates/scroll/alloy/evm/src/lib.rs @@ -0,0 +1,265 @@ +//! Alloy Evm API for Scroll. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] + +mod block; +pub use block::{ + curie, feynman, EvmExt, ReceiptBuilderCtx, ScrollBlockExecutionCtx, ScrollBlockExecutor, + ScrollBlockExecutorFactory, ScrollReceiptBuilder, ScrollTxCompressionRatios, +}; + +mod tx; +pub use tx::{ + compute_compression_ratio, FromTxWithCompressionRatio, ScrollTransactionIntoTxEnv, + ToTxWithCompressionRatio, WithCompressionRatio, +}; + +mod system_caller; + +extern crate alloc; + +use alloy_evm::{precompiles::PrecompilesMap, Database, Evm, EvmEnv, EvmFactory}; +use alloy_primitives::{Address, Bytes}; +use core::{ + fmt, + ops::{Deref, DerefMut}, +}; +use revm::{ + context::{result::HaltReason, BlockEnv, TxEnv}, + context_interface::result::{EVMError, ResultAndState}, + handler::PrecompileProvider, + inspector::NoOpInspector, + interpreter::{interpreter::EthInterpreter, InterpreterResult}, + Context, ExecuteEvm, InspectEvm, Inspector, SystemCallEvm, +}; +use revm_scroll::{ + builder::{ + DefaultScrollContext, EuclidEipActivations, FeynmanEipActivations, ScrollBuilder, + ScrollContext, + }, + instructions::ScrollInstructions, + precompile::ScrollPrecompileProvider, + ScrollSpecId, +}; + +/// Re-export `TX_L1_FEE_PRECISION_U256` from `revm-scroll` for convenience. +pub use revm_scroll::l1block::TX_L1_FEE_PRECISION_U256; + +/// Scroll EVM implementation. +#[allow(missing_debug_implementations)] +pub struct ScrollEvm<DB: Database, I, P = ScrollPrecompileProvider> { + inner: revm_scroll::ScrollEvm< + ScrollContext<DB>, + I, + ScrollInstructions<EthInterpreter, ScrollContext<DB>>, + P, + >, + inspect: bool, +} + +impl<DB: Database, I, P> ScrollEvm<DB, I, P> { + /// Creates a new instance of [`ScrollEvm`]. + pub const fn new( + inner: revm_scroll::ScrollEvm< + ScrollContext<DB>, + I, + ScrollInstructions<EthInterpreter, ScrollContext<DB>>, + P, + >, + inspect: bool, + ) -> Self { + Self { inner, inspect } + } + + /// Provides a reference to the EVM context. + pub const fn ctx(&self) -> &ScrollContext<DB> { + &self.inner.0.ctx + } + + /// Provides a mutable reference to the EVM context. + pub const fn ctx_mut(&mut self) -> &mut ScrollContext<DB> { + &mut self.inner.0.ctx + } +} + +impl<DB: Database, I, P> Deref for ScrollEvm<DB, I, P> { + type Target = ScrollContext<DB>; + + #[inline] + fn deref(&self) -> &Self::Target { + self.ctx() + } +} + +impl<DB: Database, I, P> DerefMut for ScrollEvm<DB, I, P> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + self.ctx_mut() + } +} + +impl<DB, I, P> Evm for ScrollEvm<DB, I, P> +where + DB: Database, + I: Inspector<ScrollContext<DB>>, + P: PrecompileProvider<ScrollContext<DB>, Output = InterpreterResult>, +{ + type DB = DB; + type Tx = ScrollTransactionIntoTxEnv<TxEnv>; + type Error = EVMError<DB::Error>; + type HaltReason = HaltReason; + type Spec = ScrollSpecId; + type Precompiles = P; + type Inspector = I; + + fn block(&self) -> &BlockEnv { + &self.block + } + + fn chain_id(&self) -> u64 { + self.cfg.chain_id + } + + fn transact_raw( + &mut self, + tx: Self::Tx, + ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> { + if self.inspect { + self.inner.inspect_tx(tx.into()) + } else { + self.inner.transact(tx.into()) + } + } + + fn transact_system_call( + &mut self, + caller: Address, + contract: Address, + data: Bytes, + ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> { + self.inner.transact_system_call_with_caller_finalize(caller, contract, data) + } + + fn db_mut(&mut self) -> &mut Self::DB { + &mut self.journaled_state.database + } + + fn finish(self) -> (Self::DB, EvmEnv<Self::Spec>) + where + Self: Sized, + { + let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = self.inner.0.ctx; + + (journaled_state.database, EvmEnv { block_env, cfg_env }) + } + + fn set_inspector_enabled(&mut self, enabled: bool) { + self.inspect = enabled; + } + + fn precompiles(&self) -> &Self::Precompiles { + &self.inner.0.precompiles + } + + fn precompiles_mut(&mut self) -> &mut Self::Precompiles { + &mut self.inner.0.precompiles + } + + fn inspector(&self) -> &Self::Inspector { + &self.inner.0.inspector + } + + fn inspector_mut(&mut self) -> &mut Self::Inspector { + &mut self.inner.0.inspector + } + + fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) { + ( + &self.inner.0.ctx.journaled_state.database, + &self.inner.0.inspector, + &self.inner.0.precompiles, + ) + } + + fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) { + ( + &mut self.inner.0.ctx.journaled_state.database, + &mut self.inner.0.inspector, + &mut self.inner.0.precompiles, + ) + } +} + +/// Factory producing [`ScrollEvm`]s. +#[derive(Debug, Default, Clone, Copy)] +#[non_exhaustive] +pub struct ScrollEvmFactory<P = ScrollDefaultPrecompilesFactory> { + _precompiles_factory: core::marker::PhantomData<P>, +} + +impl<P: ScrollPrecompilesFactory> EvmFactory for ScrollEvmFactory<P> { + type Evm<DB: Database, I: Inspector<ScrollContext<DB>>> = ScrollEvm<DB, I, Self::Precompiles>; + type Context<DB: Database> = ScrollContext<DB>; + type Tx = ScrollTransactionIntoTxEnv<TxEnv>; + type Error<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>; + type HaltReason = HaltReason; + type Spec = ScrollSpecId; + type Precompiles = PrecompilesMap; + + fn create_evm<DB: Database>( + &self, + db: DB, + input: EvmEnv<ScrollSpecId>, + ) -> Self::Evm<DB, NoOpInspector> { + let spec_id = input.cfg_env.spec; + ScrollEvm { + inner: Context::scroll() + .with_db(db) + .with_block(input.block_env) + .with_cfg(input.cfg_env) + .maybe_with_eip_7702() + .maybe_with_eip_7623() + .build_scroll_with_inspector(NoOpInspector {}) + .with_precompiles(P::with_spec(spec_id)), + inspect: false, + } + } + + fn create_evm_with_inspector<DB: Database, I: Inspector<Self::Context<DB>>>( + &self, + db: DB, + input: EvmEnv<ScrollSpecId>, + inspector: I, + ) -> Self::Evm<DB, I> { + let spec_id = input.cfg_env.spec; + ScrollEvm { + inner: Context::scroll() + .with_db(db) + .with_block(input.block_env) + .with_cfg(input.cfg_env) + .maybe_with_eip_7702() + .maybe_with_eip_7623() + .build_scroll_with_inspector(inspector) + .with_precompiles(P::with_spec(spec_id)), + inspect: true, + } + } +} + +/// A factory trait for creating precompiles for Scroll EVM. +pub trait ScrollPrecompilesFactory: Default + fmt::Debug { + /// Creates a new instance of precompiles for the given Scroll specification ID. + fn with_spec(spec: ScrollSpecId) -> PrecompilesMap; +} + +/// Default implementation of the Scroll precompiles factory. +#[derive(Default, Debug, Copy, Clone)] +pub struct ScrollDefaultPrecompilesFactory; + +impl ScrollPrecompilesFactory for ScrollDefaultPrecompilesFactory { + fn with_spec(spec_id: ScrollSpecId) -> PrecompilesMap { + PrecompilesMap::from_static(ScrollPrecompileProvider::new_with_spec(spec_id).precompiles()) + } +}
diff --git reth/crates/scroll/alloy/evm/src/system_caller.rs scroll-reth/crates/scroll/alloy/evm/src/system_caller.rs new file mode 100644 index 0000000000000000000000000000000000000000..83cd0033743832096d25309bdcec202fa5b5b7db --- /dev/null +++ scroll-reth/crates/scroll/alloy/evm/src/system_caller.rs @@ -0,0 +1,202 @@ +use alloc::string::ToString; + +use alloy_eips::eip2935::HISTORY_STORAGE_ADDRESS; +use alloy_evm::{ + block::{BlockExecutionError, BlockValidationError}, + Evm, +}; +use alloy_primitives::B256; +use revm::{context::result::ResultAndState, DatabaseCommit}; +use scroll_alloy_hardforks::ScrollHardforks; + +/// An ephemeral helper type for executing system calls. +/// +/// This can be used to chain system transaction calls. +#[derive(Debug)] +pub(crate) struct ScrollSystemCaller<Spec> { + spec: Spec, +} + +impl<Spec> ScrollSystemCaller<Spec> { + /// Create a new system caller with the given spec. + pub(crate) const fn new(spec: Spec) -> Self { + Self { spec } + } +} + +impl<Spec> ScrollSystemCaller<Spec> +where + Spec: ScrollHardforks, +{ + /// Applies the pre-block call to the EIP-2935 blockhashes contract. + pub(crate) fn apply_blockhashes_contract_call( + &self, + parent_block_hash: B256, + evm: &mut impl Evm<DB: DatabaseCommit>, + ) -> Result<(), BlockExecutionError> { + let result_and_state = + transact_blockhashes_contract_call(&self.spec, parent_block_hash, evm)?; + + if let Some(res) = result_and_state { + evm.db_mut().commit(res.state); + } + + Ok(()) + } +} + +/// Applies the pre-block call to the [EIP-2935] blockhashes contract, using the given block, +/// chain specification, and EVM. +/// +/// If Feynman is not activated, or the block is the genesis block, then this is a no-op, and no +/// state changes are made. +/// +/// Returns `None` if Feynman is not active or the block is the genesis block, otherwise returns the +/// result of the call. +/// +/// [EIP-2935]: https://eips.ethereum.org/EIPS/eip-2935 +#[inline] +fn transact_blockhashes_contract_call<Halt>( + spec: impl ScrollHardforks, + parent_block_hash: B256, + evm: &mut impl Evm<HaltReason = Halt>, +) -> Result<Option<ResultAndState<Halt>>, BlockExecutionError> { + // if Feynman is not active at timestamp then no system transaction occurs. + if !spec.is_feynman_active_at_timestamp(evm.block().timestamp.to()) { + return Ok(None); + } + + // if the block number is zero (genesis block) then no system transaction may occur as per + // EIP-2935 + if evm.block().number.to::<u64>() == 0u64 { + return Ok(None); + } + + let res = match evm.transact_system_call( + alloy_eips::eip4788::SYSTEM_ADDRESS, + HISTORY_STORAGE_ADDRESS, + parent_block_hash.0.into(), + ) { + Ok(res) => res, + Err(e) => { + return Err(BlockValidationError::BlockHashContractCall { message: e.to_string() }.into()) + } + }; + + Ok(Some(res)) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{convert::Infallible, sync::Arc}; + + use crate::curie::L1_GAS_PRICE_ORACLE_ADDRESS; + use alloy_consensus::{Block, BlockBody, Header}; + use alloy_eips::eip2935::HISTORY_STORAGE_CODE; + use alloy_hardforks::ForkCondition; + use alloy_primitives::{keccak256, U256}; + use reth_evm::ConfigureEvm; + use reth_scroll_chainspec::{ScrollChainConfig, ScrollChainSpecBuilder}; + use reth_scroll_evm::ScrollEvmConfig; + use revm::{ + bytecode::Bytecode, + database::{EmptyDBTyped, State}, + state::AccountInfo, + Database, + }; + use scroll_alloy_consensus::ScrollTxEnvelope; + use scroll_alloy_hardforks::{ScrollChainHardforks, ScrollHardfork}; + + #[test] + fn test_should_not_apply_blockhashes_contract_call_before_feynman() { + // initiate system caller. + let system_caller = ScrollSystemCaller::new(ScrollChainHardforks::new([ + (ScrollHardfork::EuclidV2, ForkCondition::Timestamp(0)), + (ScrollHardfork::Feynman, ForkCondition::Timestamp(100)), + ])); + + // initiate db with system contract. + let db = EmptyDBTyped::<Infallible>::new(); + let mut state = + State::builder().with_database(db).with_bundle_update().without_state_clear().build(); + state.insert_account( + HISTORY_STORAGE_ADDRESS, + AccountInfo { + code_hash: keccak256(HISTORY_STORAGE_CODE.clone()), + code: Some(Bytecode::new_raw(HISTORY_STORAGE_CODE.clone())), + ..Default::default() + }, + ); + + // load l1 oracle in state. + state.insert_account(L1_GAS_PRICE_ORACLE_ADDRESS, Default::default()); + + // prepare chain spec. + let chain_spec = + Arc::new(ScrollChainSpecBuilder::scroll_mainnet().build(ScrollChainConfig::mainnet())); + let evm_config = ScrollEvmConfig::scroll(chain_spec); + + let header = Header { + parent_hash: B256::random(), + number: 1, + gas_limit: 20_000_000, + ..Default::default() + }; + let block: Block<ScrollTxEnvelope, _> = Block { header, body: BlockBody::default() }; + + // initiate the evm and apply the block hashes contract call. + let mut evm = evm_config.evm_for_block(state, &block.header); + system_caller.apply_blockhashes_contract_call(block.parent_hash, &mut evm).unwrap(); + + // assert the storage slot remains unchanged. + let parent_hash = evm.db_mut().storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap(); + assert_eq!(parent_hash, U256::ZERO); + } + + #[test] + fn test_should_apply_blockhashes_contract_call_after_feynman() { + // initiate system caller. + let system_caller = ScrollSystemCaller::new(ScrollChainHardforks::new([( + ScrollHardfork::Feynman, + ForkCondition::Timestamp(0), + )])); + + // initiate db with system contract. + let db = EmptyDBTyped::<Infallible>::new(); + let mut state = + State::builder().with_database(db).with_bundle_update().without_state_clear().build(); + state.insert_account( + HISTORY_STORAGE_ADDRESS, + AccountInfo { + code_hash: keccak256(HISTORY_STORAGE_CODE.clone()), + code: Some(Bytecode::new_raw(HISTORY_STORAGE_CODE.clone())), + ..Default::default() + }, + ); + + // load l1 oracle in state. + state.insert_account(L1_GAS_PRICE_ORACLE_ADDRESS, Default::default()); + + // prepare chain spec. + let chain_spec = + Arc::new(ScrollChainSpecBuilder::scroll_mainnet().build(ScrollChainConfig::mainnet())); + let evm_config = ScrollEvmConfig::scroll(chain_spec); + + let header = Header { + parent_hash: B256::random(), + number: 1, + gas_limit: 20_000_000, + ..Default::default() + }; + let block: Block<ScrollTxEnvelope, _> = Block { header, body: BlockBody::default() }; + + // initiate the evm and apply the block hashes contract call. + let mut evm = evm_config.evm_for_block(state, &block.header); + system_caller.apply_blockhashes_contract_call(block.parent_hash, &mut evm).unwrap(); + + // assert the hash is written to storage. + let parent_hash = evm.db_mut().storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap(); + assert_eq!(Into::<B256>::into(parent_hash), block.parent_hash); + } +}
diff --git reth/crates/scroll/alloy/evm/src/tx/compression.rs scroll-reth/crates/scroll/alloy/evm/src/tx/compression.rs new file mode 100644 index 0000000000000000000000000000000000000000..f86c82de2562ec9854b83632e4a9740d88ede4d1 --- /dev/null +++ scroll-reth/crates/scroll/alloy/evm/src/tx/compression.rs @@ -0,0 +1,249 @@ +use super::FromRecoveredTx; +use crate::ScrollTransactionIntoTxEnv; +use alloy_consensus::transaction::Recovered; +use alloy_eips::{Encodable2718, Typed2718}; +use alloy_evm::{RecoveredTx, ToTxEnv}; +use alloy_primitives::{Address, Bytes, TxKind, U256}; +use revm::context::TxEnv; +use scroll_alloy_consensus::{ScrollTxEnvelope, TxL1Message}; +pub use zstd_compression::compute_compression_ratio; + +#[cfg(feature = "zstd_compression")] +mod zstd_compression { + use super::*; + use std::io::Write; + + use encoder_standard::{init_zstd_encoder, N_BLOCK_SIZE_TARGET}; + use revm_scroll::l1block::TX_L1_FEE_PRECISION_U256; + + /// Computes the compression ratio for the provided bytes. + /// + /// This is computed as: + /// `max(1, original_size * TX_L1_FEE_PRECISION_U256 / encoded_size)` + pub fn compute_compression_ratio<T: AsRef<[u8]>>(bytes: &T) -> U256 { + // By definition, the compression ratio of empty data is infinity + if bytes.as_ref().is_empty() { + return U256::MAX + } + + // Instantiate the compressor + let mut compressor = init_zstd_encoder(N_BLOCK_SIZE_TARGET); + + // Set the pledged source size to the length of the bytes + // and write the bytes to the compressor. + let original_bytes_len = bytes.as_ref().len(); + compressor + .set_pledged_src_size(Some(original_bytes_len as u64)) + .expect("failed to set pledged source size"); + compressor.write_all(bytes.as_ref()).expect("failed to write bytes to compressor"); + + // Finish the compression and get the result. + let result = compressor.finish().expect("failed to finish compression"); + let encoded_bytes_len = result.len(); + + // Make sure that the compression ratio >= 1.0 + if encoded_bytes_len > original_bytes_len { + return TX_L1_FEE_PRECISION_U256; + } + + // compression_ratio(tx) = size(tx) * PRECISION / size(zstd(tx)) + U256::from(original_bytes_len) + .saturating_mul(TX_L1_FEE_PRECISION_U256) + .wrapping_div(U256::from(encoded_bytes_len)) + } +} + +#[cfg(not(feature = "zstd_compression"))] +mod zstd_compression { + use super::*; + + /// Computes the compression ratio for the provided bytes. This panics if the compression + /// feature is not enabled. This is to support `no_std` environments where zstd is not + /// available. + pub fn compute_compression_ratio<T: AsRef<[u8]>>(_bytes: &T) -> U256 { + panic!("Compression feature is not enabled. Please enable the 'compression' feature to use this function."); + } +} + +/// A generic wrapper for a type that includes a compression ratio and encoded bytes. +#[derive(Debug, Clone)] +pub struct WithCompressionRatio<T> { + // The original value. + value: T, + // The compression ratio: + // compression_ratio = max(1, size(v) * 1e9 / size(compress(v))) + compression_ratio: U256, + // The raw encoded bytes of `value`, without compression. + encoded_bytes: Bytes, +} + +/// A trait for types that can be constructed from a transaction, +/// its sender, encoded bytes and compression ratio. +pub trait FromTxWithCompressionRatio<Tx> { + /// Builds a `TxEnv` from a transaction, its sender, encoded transaction bytes, + /// and a compression ratio. + fn from_tx_with_compression_ratio( + tx: &Tx, + sender: Address, + encoded: Bytes, + compression_ratio: Option<U256>, + ) -> Self; +} + +impl<TxEnv, T> FromTxWithCompressionRatio<&T> for TxEnv +where + TxEnv: FromTxWithCompressionRatio<T>, +{ + fn from_tx_with_compression_ratio( + tx: &&T, + sender: Address, + encoded: Bytes, + compression_ratio: Option<U256>, + ) -> Self { + TxEnv::from_tx_with_compression_ratio(tx, sender, encoded, compression_ratio) + } +} + +impl<T, TxEnv: FromTxWithCompressionRatio<T>> ToTxEnv<TxEnv> + for WithCompressionRatio<Recovered<T>> +{ + fn to_tx_env(&self) -> TxEnv { + let recovered = &self.value; + TxEnv::from_tx_with_compression_ratio( + recovered.inner(), + recovered.signer(), + self.encoded_bytes.clone(), + Some(self.compression_ratio), + ) + } +} + +impl<T, TxEnv: FromTxWithCompressionRatio<T>> ToTxEnv<TxEnv> + for WithCompressionRatio<&Recovered<T>> +{ + fn to_tx_env(&self) -> TxEnv { + let recovered = &self.value; + TxEnv::from_tx_with_compression_ratio( + recovered.inner(), + *recovered.signer(), + self.encoded_bytes.clone(), + Some(self.compression_ratio), + ) + } +} + +impl FromTxWithCompressionRatio<ScrollTxEnvelope> for ScrollTransactionIntoTxEnv<TxEnv> { + fn from_tx_with_compression_ratio( + tx: &ScrollTxEnvelope, + caller: Address, + encoded: Bytes, + compression_ratio: Option<U256>, + ) -> Self { + let base = match &tx { + ScrollTxEnvelope::Legacy(tx) => TxEnv::from_recovered_tx(tx.tx(), caller), + ScrollTxEnvelope::Eip2930(tx) => TxEnv::from_recovered_tx(tx.tx(), caller), + ScrollTxEnvelope::Eip1559(tx) => TxEnv::from_recovered_tx(tx.tx(), caller), + ScrollTxEnvelope::Eip7702(tx) => TxEnv::from_recovered_tx(tx.tx(), caller), + ScrollTxEnvelope::L1Message(tx) => { + let TxL1Message { to, value, gas_limit, input, queue_index: _, sender: _ } = &**tx; + TxEnv { + tx_type: tx.ty(), + caller, + gas_limit: *gas_limit, + kind: TxKind::Call(*to), + value: *value, + data: input.clone(), + ..Default::default() + } + } + }; + + Self::new(base, Some(encoded), compression_ratio) + } +} + +/// A trait that allows a type to be converted into [`WithCompressionRatio`]. +pub trait ToTxWithCompressionRatio<Tx> { + /// Converts the type into a [`WithCompressionRatio`] instance using the provided compression + /// ratio. + fn with_compression_ratio( + &self, + compression_ratio: U256, + ) -> WithCompressionRatio<Recovered<&Tx>>; +} + +impl<Tx: Encodable2718> ToTxWithCompressionRatio<Tx> for Recovered<&Tx> { + fn with_compression_ratio( + &self, + compression_ratio: U256, + ) -> WithCompressionRatio<Recovered<&Tx>> { + let encoded_bytes = self.inner().encoded_2718(); + WithCompressionRatio { + value: *self, + compression_ratio, + encoded_bytes: encoded_bytes.into(), + } + } +} + +impl<Tx, T: RecoveredTx<Tx>> RecoveredTx<Tx> for WithCompressionRatio<T> { + fn tx(&self) -> &Tx { + self.value.tx() + } + + fn signer(&self) -> &Address { + self.value.signer() + } +} + +#[cfg(test)] +mod tests { + use super::compute_compression_ratio; + use alloy_primitives::{bytes, U256}; + + #[test] + fn test_compute_compression_ratio() -> eyre::Result<()> { + // eth-transfer + let bytes = bytes!("0x"); + let ratio = compute_compression_ratio(&bytes); + assert_eq!(ratio, U256::MAX); // empty data is infinitely compressible by definition + + // scr-transfer + // https://scrollscan.com/tx/0x7b681ce914c9774aff364d2b099b2ba41dea44bcd59dbebb9d4c4b6853893179 + let bytes = bytes!("0xa9059cbb000000000000000000000000687b50a70d33d71f9a82dd330b8c091e4d77250800000000000000000000000000000000000000000000000ac96dda943e512bb9"); + let ratio = compute_compression_ratio(&bytes); + assert_eq!(ratio, U256::from(1_387_755_102u64)); // 1.4x + + // syncswap-swap + // https://scrollscan.com/tx/0x59a7b72503400b6719f3cb670c7b1e7e45ce5076f30b98bdaad3b07a5d0fbc02 + let bytes = bytes!("0x2cc4081e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000005ec79b80000000000000000000000000000000000000000000000000003328b944c400000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000053000000000000000000000000000000000000040000000000000000000000000000000000000000000000000091a94863ca800000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000814a23b053fd0f102aeeda0459215c2444799c7000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000600000000000000000000000005300000000000000000000000000000000000004000000000000000000000000485ca81b70255da2fe3fd0814b57d1b08fce784e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000"); + let ratio = compute_compression_ratio(&bytes); + assert_eq!(ratio, U256::from(4_857_142_857u64)); // 4.8x + + // uniswap-swap + // https://scrollscan.com/tx/0x65b268bd8ef416f44983ee277d748de044243272b0f106b71ff03cc8501a05da + let bytes = bytes!("0x5023b4df00000000000000000000000006efdbff2a14a7c8e15944d1f4a48f9f95f663a4000000000000000000000000530000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000485ca81b70255da2fe3fd0814b57d1b08fce784e000000000000000000000000000000000000000000000000006a94d74f43000000000000000000000000000000000000000000000000000000000000045af6750000000000000000000000000000000000000000000000000000000000000000"); + let ratio = compute_compression_ratio(&bytes); + assert_eq!(ratio, U256::from(2_620_689_655u64)); // 2.6x + + // etherfi-deposit + // https://scrollscan.com/tx/0x41a77736afd54134b6c673e967c9801e326495074012b4033bd557920cbe5a71 + let bytes = bytes!("0x63baa26000000000000000000000000077a7e3215a621a9935d32a046212ebfcffa3bff900000000000000000000000006efdbff2a14a7c8e15944d1f4a48f9f95f663a400000000000000000000000008c6f91e2b681faf5e17227f2a44c307b3c1364c0000000000000000000000000000000000000000000000000000000002d4cae000000000000000000000000000000000000000000000000000000000028f7f83000000000000000000000000249e3fa81d73244f956ecd529715323b6d02f24b00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000041a95314c3a11f86cc673f2afd60d27f559cb2edcc0da5af030adffc97f9a5edc3314efbadd32878e289017f644a4afa365da5367fefe583f7c4ff0c6047e2c1ff1b00000000000000000000000000000000000000000000000000000000000000"); + let ratio = compute_compression_ratio(&bytes); + assert_eq!(ratio, U256::from(1_788_944_723u64)); // 1.8x + + // edgepushoracle-postupdate + // https://scrollscan.com/tx/0x8271c68146a3b07b1ebf52ce0b550751f49cbd72fa0596ef14ff56d1f23a0bec + let bytes = bytes!("0x49a1a4fb000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000005f725f60000000000000000000000000000000000000000000000000000000003d0cac600000000000000000000000000000000000000000000000000000000685d50cd000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000004155903b95865fc5a5dd7d4d876456140dd0b815695647fc41eb1924f4cfe267265130b5a5d77125c44cf6a5a81edba6d5850ba00f90ab83281c9b44e17528fd74010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000416f000e0498733998e6a1a6454e116c1b1f95f7e000400b6a54029406cf288bdc615b62de8e2db533d6010ca57001e0b8a4b3f05ed516a31830516c52b9df206e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000410dabc77a807d729ff62c3be740d492d884f026ad2770fa7c4bdec569e201643656b07f2009d2129173738571417734a3df051cebc7b8233bec6d9471c21c098700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041eb009614c939170e9ff3d3e06c3a2c45810fe46a364ce28ecec5e220f5fd86cd6e0f70ab9093dd6b22b69980246496b600c8fcb054047962d4128efa48b692f301000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041a31b4dd4f0a482372d75c7a8c5f11aa8084a5f358579866f1d25a26a15beb2b5153400bfa7fa3d6fba138c02dd1eb8a5a97d62178d98c5632a153396a566e5ed0000000000000000000000000000000000000000000000000000000000000000"); + let ratio = compute_compression_ratio(&bytes); + assert_eq!(ratio, U256::from(2_441_805_225u64)); // 2.4x + + // intmax-post + // https://scrollscan.com/tx/0x7244e27223cdd79ba0f0e3990c746e5d524e35dbcc200f0a7e664ffdc6d08eef + let bytes = bytes!("0x9b6babf0f0372bb253e060ecbdd3dbef8b832b0e743148bd807bfcf665593a56a18bac69000000000000000000000000000000000000000000000000000000006861676d0000000000000000000000000000000000000000000000000000000000000015800000000000000000000000000000000000000000000000000000000000000029a690c4ef1e18884a11f73c8595fb721f964a3e2bee809800c474278f024bcd05a76119827e6c464cee8620f616a9a23d41305eb9f9682f9d2eaf964325fcd71147783453566f27ce103a2398d96719ee22ba51b89b92cdf952af817929329403b75ae310b23cf250041d53c82bef431fa2527e2dd68b49f45f06feb2bd09f011358fe2650b8987ea2bb39bb6e28ce770f4fc9c4f064d0ae7573a1450452b501a5b0d3454d254dbf9db7094f4ca1f5056143f5c70dee4126443a6150d9e51bd05dac7e9a2bd48a8797ac6e9379d400c5ce1815b10846eaf0d80dca3a727ffd0075387e0f1bc1b363c81ecf8d05a4b654ac6fbe1cdc7c741a5c0bbeabde4138906009129ca033af12094fd7306562d9735b2fe757f021b7eb3320f8a814a286a10130969de2783e49871b80e967cfba630e6bdef2fd1d2b1076c6c3f5fd9ae5800000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000000012a98f1556efe81340fad3e59044b8139ce62f1d5d50b44b680de9422b1ddbf1a"); + let ratio = compute_compression_ratio(&bytes); + assert_eq!(ratio, U256::from(1_298_578_199u64)); // 1.3x + + Ok(()) + } +}
diff --git reth/crates/scroll/alloy/evm/src/tx/mod.rs scroll-reth/crates/scroll/alloy/evm/src/tx/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..b9eba7510d320dbfde7b22587969e499c4318e25 --- /dev/null +++ scroll-reth/crates/scroll/alloy/evm/src/tx/mod.rs @@ -0,0 +1,276 @@ +use alloy_consensus::crypto::secp256k1::recover_signer; +use alloy_eips::{Encodable2718, Typed2718}; +use alloy_evm::{FromRecoveredTx, FromTxWithEncoded, IntoTxEnv}; +use alloy_primitives::{Address, Bytes, TxKind, B256, U256}; +use core::ops::{Deref, DerefMut}; +use revm::context::{ + either::Either, + transaction::{RecoveredAuthority, RecoveredAuthorization}, + Transaction, TxEnv, +}; +use revm_scroll::ScrollTransaction; +use scroll_alloy_consensus::{ScrollTxEnvelope, TxL1Message, L1_MESSAGE_TRANSACTION_TYPE}; + +mod compression; +pub use compression::{ + compute_compression_ratio, FromTxWithCompressionRatio, ToTxWithCompressionRatio, + WithCompressionRatio, +}; + +/// This structure wraps around a [`ScrollTransaction`] and allows us to implement the [`IntoTxEnv`] +/// trait. This can be removed when the interface is improved. Without this wrapper, we would need +/// to implement the trait in `revm-scroll`, which adds a dependency on `alloy-evm` in the crate. +/// Any changes to `alloy-evm` would require changes to `revm-scroll` which isn't desired. +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ScrollTransactionIntoTxEnv<T: Transaction>(ScrollTransaction<T>); + +impl<T: Transaction> ScrollTransactionIntoTxEnv<T> { + /// Returns a new [`ScrollTransactionIntoTxEnv`]. + pub fn new(base: T, rlp_bytes: Option<Bytes>, compression_ratio: Option<U256>) -> Self { + Self(ScrollTransaction::new(base, rlp_bytes, compression_ratio)) + } +} + +impl<T: Transaction> From<ScrollTransaction<T>> for ScrollTransactionIntoTxEnv<T> { + fn from(value: ScrollTransaction<T>) -> Self { + Self(value) + } +} + +impl<T: Transaction> From<ScrollTransactionIntoTxEnv<T>> for ScrollTransaction<T> { + fn from(value: ScrollTransactionIntoTxEnv<T>) -> Self { + value.0 + } +} + +impl<T: Transaction> Deref for ScrollTransactionIntoTxEnv<T> { + type Target = ScrollTransaction<T>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<T: Transaction> DerefMut for ScrollTransactionIntoTxEnv<T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<T: Transaction> IntoTxEnv<Self> for ScrollTransactionIntoTxEnv<T> { + fn into_tx_env(self) -> Self { + self + } +} + +impl<T: Transaction> Transaction for ScrollTransactionIntoTxEnv<T> { + type AccessListItem<'a> + = T::AccessListItem<'a> + where + T: 'a; + type Authorization<'a> + = T::Authorization<'a> + where + T: 'a; + + fn tx_type(&self) -> u8 { + self.0.tx_type() + } + + fn caller(&self) -> Address { + self.0.caller() + } + + fn gas_limit(&self) -> u64 { + self.0.gas_limit() + } + + fn value(&self) -> U256 { + self.0.value() + } + + fn input(&self) -> &Bytes { + self.0.input() + } + + fn nonce(&self) -> u64 { + self.0.nonce() + } + + fn kind(&self) -> TxKind { + self.0.kind() + } + + fn chain_id(&self) -> Option<u64> { + self.0.chain_id() + } + + fn gas_price(&self) -> u128 { + self.0.gas_price() + } + + fn access_list( + &self, + ) -> Option<impl Iterator<Item = <ScrollTransaction<T> as Transaction>::AccessListItem<'_>>> + { + self.0.access_list() + } + + fn blob_versioned_hashes(&self) -> &[B256] { + self.0.blob_versioned_hashes() + } + + fn max_fee_per_blob_gas(&self) -> u128 { + self.0.max_fee_per_blob_gas() + } + + fn authorization_list_len(&self) -> usize { + self.0.authorization_list_len() + } + + fn authorization_list(&self) -> impl Iterator<Item = Self::Authorization<'_>> { + self.0.authorization_list() + } + + fn max_priority_fee_per_gas(&self) -> Option<u128> { + self.0.max_priority_fee_per_gas() + } +} + +impl FromTxWithEncoded<ScrollTxEnvelope> for ScrollTransactionIntoTxEnv<TxEnv> { + fn from_encoded_tx(tx: &ScrollTxEnvelope, caller: Address, encoded: Bytes) -> Self { + let base = match &tx { + ScrollTxEnvelope::Legacy(tx) => TxEnv::from_recovered_tx(tx.tx(), caller), + ScrollTxEnvelope::Eip2930(tx) => TxEnv::from_recovered_tx(tx.tx(), caller), + ScrollTxEnvelope::Eip1559(tx) => TxEnv::from_recovered_tx(tx.tx(), caller), + ScrollTxEnvelope::Eip7702(tx) => TxEnv::from_recovered_tx(tx.tx(), caller), + ScrollTxEnvelope::L1Message(tx) => { + let TxL1Message { to, value, gas_limit, input, queue_index: _, sender: _ } = &**tx; + TxEnv { + tx_type: tx.ty(), + caller, + gas_limit: *gas_limit, + kind: TxKind::Call(*to), + value: *value, + data: input.clone(), + ..Default::default() + } + } + }; + + let encoded = (!tx.is_l1_message()).then_some(encoded); + // Note: We compute the transaction ratio on tx.data, not on the full encoded transaction. + let compression_ratio = compute_compression_ratio(base.input()); + Self::new(base, encoded, Some(compression_ratio)) + } +} + +impl FromRecoveredTx<ScrollTxEnvelope> for ScrollTransactionIntoTxEnv<TxEnv> { + fn from_recovered_tx(tx: &ScrollTxEnvelope, sender: Address) -> Self { + let envelope = tx.encoded_2718(); + + let base = match &tx { + ScrollTxEnvelope::Legacy(tx) => TxEnv { + gas_limit: tx.tx().gas_limit, + gas_price: tx.tx().gas_price, + gas_priority_fee: None, + kind: tx.tx().to, + value: tx.tx().value, + data: tx.tx().input.clone(), + chain_id: tx.tx().chain_id, + nonce: tx.tx().nonce, + access_list: Default::default(), + blob_hashes: Default::default(), + max_fee_per_blob_gas: Default::default(), + authorization_list: Default::default(), + tx_type: 0, + caller: sender, + }, + ScrollTxEnvelope::Eip2930(tx) => TxEnv { + gas_limit: tx.tx().gas_limit, + gas_price: tx.tx().gas_price, + gas_priority_fee: None, + kind: tx.tx().to, + value: tx.tx().value, + data: tx.tx().input.clone(), + chain_id: Some(tx.tx().chain_id), + nonce: tx.tx().nonce, + access_list: tx.tx().access_list.clone(), + blob_hashes: Default::default(), + max_fee_per_blob_gas: Default::default(), + authorization_list: Default::default(), + tx_type: 1, + caller: sender, + }, + ScrollTxEnvelope::Eip1559(tx) => TxEnv { + gas_limit: tx.tx().gas_limit, + gas_price: tx.tx().max_fee_per_gas, + gas_priority_fee: Some(tx.tx().max_priority_fee_per_gas), + kind: tx.tx().to, + value: tx.tx().value, + data: tx.tx().input.clone(), + chain_id: Some(tx.tx().chain_id), + nonce: tx.tx().nonce, + access_list: tx.tx().access_list.clone(), + blob_hashes: Default::default(), + max_fee_per_blob_gas: Default::default(), + authorization_list: Default::default(), + tx_type: 2, + caller: sender, + }, + ScrollTxEnvelope::Eip7702(tx) => TxEnv { + gas_limit: tx.tx().gas_limit, + gas_price: tx.tx().max_fee_per_gas, + gas_priority_fee: Some(tx.tx().max_priority_fee_per_gas), + kind: tx.tx().to.into(), + value: tx.tx().value, + data: tx.tx().input.clone(), + chain_id: Some(tx.tx().chain_id), + nonce: tx.tx().nonce, + access_list: tx.tx().access_list.clone(), + blob_hashes: Default::default(), + max_fee_per_blob_gas: Default::default(), + authorization_list: tx + .tx() + .authorization_list + .iter() + .map(|auth| { + Either::Right(RecoveredAuthorization::new_unchecked( + auth.inner().clone(), + auth.signature() + .ok() + .and_then(|signature| { + recover_signer(&signature, auth.signature_hash()).ok() + }) + .map_or(RecoveredAuthority::Invalid, RecoveredAuthority::Valid), + )) + }) + .collect(), + tx_type: 4, + caller: sender, + }, + ScrollTxEnvelope::L1Message(tx) => TxEnv { + gas_limit: tx.gas_limit, + gas_price: 0, + gas_priority_fee: None, + kind: TxKind::Call(tx.to), + value: tx.value, + data: tx.input.clone(), + chain_id: None, + nonce: 0, + access_list: Default::default(), + blob_hashes: Default::default(), + max_fee_per_blob_gas: Default::default(), + authorization_list: Default::default(), + tx_type: L1_MESSAGE_TRANSACTION_TYPE, + caller: sender, + }, + }; + + let encoded = (!tx.is_l1_message()).then_some(envelope.into()); + // Note: We compute the transaction ratio on tx.data, not on the full encoded transaction. + let compression_ratio = compute_compression_ratio(base.input()); + Self::new(base, encoded, Some(compression_ratio)) + } +}
diff --git reth/crates/scroll/alloy/hardforks/Cargo.toml scroll-reth/crates/scroll/alloy/hardforks/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..7dea06ea9bbaa8c5bc6fbc2477fb2c49420db09a --- /dev/null +++ scroll-reth/crates/scroll/alloy/hardforks/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "scroll-alloy-hardforks" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# alloy +alloy-hardforks.workspace = true + +# misc +auto_impl = { workspace = true, default-features = false } +serde = { workspace = true, optional = true } + +[features] +default = ["std"] +std = ["serde?/std"] +serde = ["dep:serde", "alloy-hardforks/serde"]
diff --git reth/crates/scroll/alloy/hardforks/src/hardfork.rs scroll-reth/crates/scroll/alloy/hardforks/src/hardfork.rs new file mode 100644 index 0000000000000000000000000000000000000000..dce4ac29147e97113a3029c2c161ab7218a2b399 --- /dev/null +++ scroll-reth/crates/scroll/alloy/hardforks/src/hardfork.rs @@ -0,0 +1,87 @@ +//! Hard forks of scroll protocol. + +use alloy_hardforks::{hardfork, ForkCondition}; + +hardfork!( + /// The name of the Scroll hardfork + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + ScrollHardfork { + /// Archimedes: scroll test hardfork. + Archimedes, + /// Bernoulli: <https://scroll.io/blog/blobs-are-here-scrolls-bernoulli-upgrade>. + Bernoulli, + /// Curie: <https://scroll.io/blog/compressing-the-gas-scrolls-curie-upgrade>. + Curie, + /// Darwin: <https://scroll.io/blog/proof-recursion-scrolls-darwin-upgrade>. + Darwin, + /// DarwinV2 <https://x.com/Scroll_ZKP/status/1830565514755584269>. + DarwinV2, + /// Euclid <https://docs.scroll.io/en/technology/overview/scroll-upgrades/euclid-upgrade/> + Euclid, + /// EuclidV2 <https://docs.scroll.io/en/technology/overview/scroll-upgrades/euclid-upgrade/> + EuclidV2, + /// Feynman <https://docs.scroll.io/en/technology/overview/scroll-upgrades/feynman-upgrade/> + Feynman + } +); + +impl ScrollHardfork { + /// Scroll mainnet list of hardforks. + pub const fn scroll_mainnet() -> [(Self, ForkCondition); 8] { + [ + (Self::Archimedes, ForkCondition::Block(0)), + (Self::Bernoulli, ForkCondition::Block(5220340)), + (Self::Curie, ForkCondition::Block(7096836)), + (Self::Darwin, ForkCondition::Timestamp(1724227200)), + (Self::DarwinV2, ForkCondition::Timestamp(1725264000)), + (Self::Euclid, ForkCondition::Timestamp(1744815600)), + (Self::EuclidV2, ForkCondition::Timestamp(1745305200)), + (Self::Feynman, ForkCondition::Timestamp(1755576000)), + ] + } + + /// Scroll sepolia list of hardforks. + pub const fn scroll_sepolia() -> [(Self, ForkCondition); 8] { + [ + (Self::Archimedes, ForkCondition::Block(0)), + (Self::Bernoulli, ForkCondition::Block(3747132)), + (Self::Curie, ForkCondition::Block(4740239)), + (Self::Darwin, ForkCondition::Timestamp(1723622400)), + (Self::DarwinV2, ForkCondition::Timestamp(1724832000)), + (Self::Euclid, ForkCondition::Timestamp(1741680000)), + (Self::EuclidV2, ForkCondition::Timestamp(1741852800)), + (Self::Feynman, ForkCondition::Timestamp(1753167600)), + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + #[test] + fn check_scroll_hardfork_from_str() { + let hardfork_str = + ["BernOulLi", "CUrie", "DaRwIn", "DaRwInV2", "EUcliD", "eUClidv2", "FEYnmaN"]; + let expected_hardforks = [ + ScrollHardfork::Bernoulli, + ScrollHardfork::Curie, + ScrollHardfork::Darwin, + ScrollHardfork::DarwinV2, + ScrollHardfork::Euclid, + ScrollHardfork::EuclidV2, + ScrollHardfork::Feynman, + ]; + + let hardforks: Vec<ScrollHardfork> = + hardfork_str.iter().map(|h| ScrollHardfork::from_str(h).unwrap()).collect(); + + assert_eq!(hardforks, expected_hardforks); + } + + #[test] + fn check_nonexistent_hardfork_from_str() { + assert!(ScrollHardfork::from_str("not a hardfork").is_err()); + } +}
diff --git reth/crates/scroll/alloy/hardforks/src/lib.rs scroll-reth/crates/scroll/alloy/hardforks/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..a4b755ac03b36069fdf976e335a45e4994873fd3 --- /dev/null +++ scroll-reth/crates/scroll/alloy/hardforks/src/lib.rs @@ -0,0 +1,104 @@ +//! Scroll-Reth hard forks. + +#![cfg_attr(not(feature = "std"), no_std)] +#[cfg(not(feature = "std"))] +extern crate alloc as std; + +use alloy_hardforks::{EthereumHardfork, EthereumHardforks, ForkCondition}; +use std::vec::Vec; + +pub use hardfork::ScrollHardfork; +pub mod hardfork; + +/// Extends [`EthereumHardforks`] with scroll helper methods. +#[auto_impl::auto_impl(&, Arc)] +pub trait ScrollHardforks: EthereumHardforks { + /// Retrieves [`ForkCondition`] by an [`ScrollHardfork`]. If `fork` is not present, returns + /// [`ForkCondition::Never`]. + fn scroll_fork_activation(&self, fork: ScrollHardfork) -> ForkCondition; + + /// Convenience method to check if [`Bernoulli`](ScrollHardfork::Bernoulli) is active at a given + /// block number. + fn is_bernoulli_active_at_block(&self, block_number: u64) -> bool { + self.scroll_fork_activation(ScrollHardfork::Bernoulli).active_at_block(block_number) + } + + /// Returns `true` if [`Curie`](ScrollHardfork::Curie) is active at given block block number. + fn is_curie_active_at_block(&self, block_number: u64) -> bool { + self.scroll_fork_activation(ScrollHardfork::Curie).active_at_block(block_number) + } + + /// Returns `true` if [`Darwin`](ScrollHardfork::Darwin) is active at given block timestamp. + fn is_darwin_active_at_timestamp(&self, timestamp: u64) -> bool { + self.scroll_fork_activation(ScrollHardfork::Darwin).active_at_timestamp(timestamp) + } + + /// Returns `true` if [`DarwinV2`](ScrollHardfork::DarwinV2) is active at given block timestamp. + fn is_darwin_v2_active_at_timestamp(&self, timestamp: u64) -> bool { + self.scroll_fork_activation(ScrollHardfork::DarwinV2).active_at_timestamp(timestamp) + } + + /// Returns `true` if [`Euclid`](ScrollHardfork::Euclid) is active at given block timestamp. + fn is_euclid_active_at_timestamp(&self, timestamp: u64) -> bool { + self.scroll_fork_activation(ScrollHardfork::Euclid).active_at_timestamp(timestamp) + } + + /// Returns `true` if [`EuclidV2`](ScrollHardfork::EuclidV2) is active at given block timestamp. + fn is_euclid_v2_active_at_timestamp(&self, timestamp: u64) -> bool { + self.scroll_fork_activation(ScrollHardfork::EuclidV2).active_at_timestamp(timestamp) + } + + /// Returns `true` if [`Feynman`](ScrollHardfork::Feynman) is active at given block timestamp. + fn is_feynman_active_at_timestamp(&self, timestamp: u64) -> bool { + self.scroll_fork_activation(ScrollHardfork::Feynman).active_at_timestamp(timestamp) + } +} + +/// A type allowing to configure activation [`ForkCondition`]s for a given list of +/// [`ScrollHardfork`]s. +#[derive(Debug, Clone)] +pub struct ScrollChainHardforks { + /// Scroll hardfork activations. + forks: Vec<(ScrollHardfork, ForkCondition)>, +} + +impl ScrollChainHardforks { + /// Creates a new [`ScrollChainHardforks`] with the given list of forks. + pub fn new(forks: impl IntoIterator<Item = (ScrollHardfork, ForkCondition)>) -> Self { + let mut forks = forks.into_iter().collect::<Vec<_>>(); + forks.sort(); + Self { forks } + } + + /// Creates a new [`ScrollChainHardforks`] with Scroll mainnet configuration. + pub fn scroll_mainnet() -> Self { + Self::new(ScrollHardfork::scroll_mainnet()) + } + + /// Creates a new [`ScrollChainHardforks`] with Scroll Sepolia configuration. + pub fn scroll_sepolia() -> Self { + Self::new(ScrollHardfork::scroll_sepolia()) + } +} + +impl EthereumHardforks for ScrollChainHardforks { + fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition { + if fork < EthereumHardfork::ArrowGlacier { + ForkCondition::Block(0) + } else if fork <= EthereumHardfork::Shanghai { + self.scroll_fork_activation(ScrollHardfork::Bernoulli) + } else { + ForkCondition::Never + } + } +} + +impl ScrollHardforks for ScrollChainHardforks { + fn scroll_fork_activation(&self, fork: ScrollHardfork) -> ForkCondition { + let Ok(idx) = self.forks.binary_search_by(|(f, _)| f.cmp(&fork)) else { + return ForkCondition::Never; + }; + + self.forks[idx].1 + } +}
diff --git reth/crates/scroll/alloy/network/Cargo.toml scroll-reth/crates/scroll/alloy/network/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..24449a64ec846f1f7984597a68bbe66f0ecb9a4a --- /dev/null +++ scroll-reth/crates/scroll/alloy/network/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "scroll-alloy-network" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +alloy-provider = { workspace = true, default-features = false } + +scroll-alloy-consensus = { workspace = true, default-features = false } +scroll-alloy-rpc-types = { workspace = true, default-features = false } + +# alloy +alloy-consensus = { workspace = true, default-features = false } +alloy-network = { workspace = true, default-features = false } +alloy-primitives = { workspace = true, default-features = false } +alloy-rpc-types-eth = { workspace = true, default-features = false } +alloy-signer = { workspace = true, default-features = false } + +[features] +std = [ + "alloy-consensus/std", + "alloy-primitives/std", + "alloy-rpc-types-eth/std", + "scroll-alloy-consensus/std", + "scroll-alloy-rpc-types/std", +]
diff --git reth/crates/scroll/alloy/network/README.md scroll-reth/crates/scroll/alloy/network/README.md new file mode 100644 index 0000000000000000000000000000000000000000..da1f2fd66158a70e84a5a764eb413aea6be31f8f --- /dev/null +++ scroll-reth/crates/scroll/alloy/network/README.md @@ -0,0 +1,8 @@ +# scroll-alloy-network + +Scroll blockchain RPC behavior abstraction. + +This crate contains a simple abstraction of the RPC behavior of an +Scroll blockchain. It is intended to be used by the Alloy client to +provide a consistent interface to the rest of the library, regardless of +changes the underlying blockchain makes to the RPC interface.
diff --git reth/crates/scroll/alloy/network/src/lib.rs scroll-reth/crates/scroll/alloy/network/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..3bfe222c55f1e3d8954d61721a981fef5538dfb7 --- /dev/null +++ scroll-reth/crates/scroll/alloy/network/src/lib.rs @@ -0,0 +1,245 @@ +#![doc = include_str!("../README.md")] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use alloy_consensus::{TxEnvelope, TxType, TypedTransaction}; +pub use alloy_network::*; +use alloy_primitives::{Address, Bytes, ChainId, TxKind, U256}; +use alloy_provider::fillers::{ + ChainIdFiller, GasFiller, JoinFill, NonceFiller, RecommendedFillers, +}; +use alloy_rpc_types_eth::AccessList; +use scroll_alloy_consensus::{self, ScrollTxEnvelope, ScrollTxType, ScrollTypedTransaction}; +use scroll_alloy_rpc_types::ScrollTransactionRequest; + +/// Types for a Scroll-stack network. +#[derive(Clone, Copy, Debug)] +pub struct Scroll { + _private: (), +} + +impl Network for Scroll { + type TxType = ScrollTxType; + + type TxEnvelope = scroll_alloy_consensus::ScrollTxEnvelope; + + type UnsignedTx = scroll_alloy_consensus::ScrollTypedTransaction; + + type ReceiptEnvelope = scroll_alloy_consensus::ScrollReceiptEnvelope; + + type Header = alloy_consensus::Header; + + type TransactionRequest = scroll_alloy_rpc_types::ScrollTransactionRequest; + + type TransactionResponse = scroll_alloy_rpc_types::Transaction; + + type ReceiptResponse = scroll_alloy_rpc_types::ScrollTransactionReceipt; + + type HeaderResponse = alloy_rpc_types_eth::Header; + + type BlockResponse = + alloy_rpc_types_eth::Block<Self::TransactionResponse, Self::HeaderResponse>; +} + +impl TransactionBuilder<Scroll> for ScrollTransactionRequest { + fn chain_id(&self) -> Option<ChainId> { + self.as_ref().chain_id() + } + + fn set_chain_id(&mut self, chain_id: ChainId) { + self.as_mut().set_chain_id(chain_id); + } + + fn nonce(&self) -> Option<u64> { + self.as_ref().nonce() + } + + fn set_nonce(&mut self, nonce: u64) { + self.as_mut().set_nonce(nonce); + } + + fn take_nonce(&mut self) -> Option<u64> { + self.as_mut().nonce.take() + } + + fn input(&self) -> Option<&Bytes> { + self.as_ref().input() + } + + fn set_input<T: Into<Bytes>>(&mut self, input: T) { + self.as_mut().set_input(input); + } + + fn from(&self) -> Option<Address> { + self.as_ref().from() + } + + fn set_from(&mut self, from: Address) { + self.as_mut().set_from(from); + } + + fn kind(&self) -> Option<TxKind> { + self.as_ref().kind() + } + + fn clear_kind(&mut self) { + self.as_mut().clear_kind(); + } + + fn set_kind(&mut self, kind: TxKind) { + self.as_mut().set_kind(kind); + } + + fn value(&self) -> Option<U256> { + self.as_ref().value() + } + + fn set_value(&mut self, value: U256) { + self.as_mut().set_value(value); + } + + fn gas_price(&self) -> Option<u128> { + self.as_ref().gas_price() + } + + fn set_gas_price(&mut self, gas_price: u128) { + self.as_mut().set_gas_price(gas_price); + } + + fn max_fee_per_gas(&self) -> Option<u128> { + self.as_ref().max_fee_per_gas() + } + + fn set_max_fee_per_gas(&mut self, max_fee_per_gas: u128) { + self.as_mut().set_max_fee_per_gas(max_fee_per_gas); + } + + fn max_priority_fee_per_gas(&self) -> Option<u128> { + self.as_ref().max_priority_fee_per_gas() + } + + fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: u128) { + self.as_mut().set_max_priority_fee_per_gas(max_priority_fee_per_gas); + } + + fn gas_limit(&self) -> Option<u64> { + self.as_ref().gas_limit() + } + + fn set_gas_limit(&mut self, gas_limit: u64) { + self.as_mut().set_gas_limit(gas_limit); + } + + fn access_list(&self) -> Option<&AccessList> { + self.as_ref().access_list() + } + + fn set_access_list(&mut self, access_list: AccessList) { + self.as_mut().set_access_list(access_list); + } + + fn complete_type(&self, ty: ScrollTxType) -> Result<(), Vec<&'static str>> { + match ty { + ScrollTxType::L1Message => Err(vec!["not implemented for L1 message tx"]), + _ => { + let ty = TxType::try_from(ty as u8).unwrap(); + self.as_ref().complete_type(ty) + } + } + } + + fn can_submit(&self) -> bool { + self.as_ref().can_submit() + } + + fn can_build(&self) -> bool { + self.as_ref().can_build() + } + + #[doc(alias = "output_transaction_type")] + fn output_tx_type(&self) -> ScrollTxType { + match self.as_ref().preferred_type() { + TxType::Eip1559 | TxType::Eip4844 => ScrollTxType::Eip1559, + TxType::Eip2930 => ScrollTxType::Eip2930, + TxType::Legacy => ScrollTxType::Legacy, + TxType::Eip7702 => ScrollTxType::Eip7702, + } + } + + #[doc(alias = "output_transaction_type_checked")] + fn output_tx_type_checked(&self) -> Option<ScrollTxType> { + self.as_ref().buildable_type().map(|tx_ty| match tx_ty { + TxType::Eip1559 | TxType::Eip4844 => ScrollTxType::Eip1559, + TxType::Eip2930 => ScrollTxType::Eip2930, + TxType::Legacy => ScrollTxType::Legacy, + TxType::Eip7702 => ScrollTxType::Eip7702, + }) + } + + fn prep_for_submission(&mut self) { + self.as_mut().prep_for_submission(); + } + + fn build_unsigned(self) -> BuildResult<ScrollTypedTransaction, Scroll> { + if let Err((tx_type, missing)) = self.as_ref().missing_keys() { + let tx_type = ScrollTxType::try_from(tx_type as u8).unwrap(); + return Err(TransactionBuilderError::InvalidTransactionRequest(tx_type, missing) + .into_unbuilt(self)); + } + Ok(self.build_typed_tx().expect("checked by missing_keys")) + } + + async fn build<W: NetworkWallet<Scroll>>( + self, + wallet: &W, + ) -> Result<<Scroll as Network>::TxEnvelope, TransactionBuilderError<Scroll>> { + Ok(wallet.sign_request(self).await?) + } +} + +impl NetworkWallet<Scroll> for EthereumWallet { + fn default_signer_address(&self) -> Address { + NetworkWallet::<Ethereum>::default_signer_address(self) + } + + fn has_signer_for(&self, address: &Address) -> bool { + NetworkWallet::<Ethereum>::has_signer_for(self, address) + } + + fn signer_addresses(&self) -> impl Iterator<Item = Address> { + NetworkWallet::<Ethereum>::signer_addresses(self) + } + + async fn sign_transaction_from( + &self, + sender: Address, + tx: ScrollTypedTransaction, + ) -> alloy_signer::Result<ScrollTxEnvelope> { + let tx = match tx { + ScrollTypedTransaction::Legacy(tx) => TypedTransaction::Legacy(tx), + ScrollTypedTransaction::Eip2930(tx) => TypedTransaction::Eip2930(tx), + ScrollTypedTransaction::Eip1559(tx) => TypedTransaction::Eip1559(tx), + ScrollTypedTransaction::Eip7702(tx) => TypedTransaction::Eip7702(tx), + ScrollTypedTransaction::L1Message(_) => { + return Err(alloy_signer::Error::other("not implemented for deposit tx")) + } + }; + let tx = NetworkWallet::<Ethereum>::sign_transaction_from(self, sender, tx).await?; + + Ok(match tx { + TxEnvelope::Eip1559(tx) => ScrollTxEnvelope::Eip1559(tx), + TxEnvelope::Eip2930(tx) => ScrollTxEnvelope::Eip2930(tx), + TxEnvelope::Eip7702(tx) => ScrollTxEnvelope::Eip7702(tx), + TxEnvelope::Legacy(tx) => ScrollTxEnvelope::Legacy(tx), + _ => unreachable!(), + }) + } +} + +impl RecommendedFillers for Scroll { + type RecommendedFillers = JoinFill<GasFiller, JoinFill<NonceFiller, ChainIdFiller>>; + + fn recommended_fillers() -> Self::RecommendedFillers { + Default::default() + } +}
diff --git reth/crates/scroll/alloy/provider/Cargo.toml scroll-reth/crates/scroll/alloy/provider/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..907b5856f33355850b3161d5f4e248f94580fd63 --- /dev/null +++ scroll-reth/crates/scroll/alloy/provider/Cargo.toml @@ -0,0 +1,77 @@ +[package] +name = "scroll-alloy-provider" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# alloy +alloy-provider.workspace = true +alloy-primitives.workspace = true +alloy-rpc-types-engine = { workspace = true, features = ["serde"] } +alloy-rpc-client.workspace = true +alloy-transport.workspace = true +alloy-transport-http = { workspace = true, features = ["jwt-auth"] } + +# scroll +scroll-alloy-network.workspace = true +scroll-alloy-rpc-types-engine = { workspace = true, features = ["serde"] } + +# reth +reth-rpc-api = { workspace = true, features = ["client"] } + +# reth-scroll +reth-scroll-engine-primitives.workspace = true + +# misc +async-trait.workspace = true +derive_more.workspace = true +eyre.workspace = true +http-body-util.workspace = true +reqwest.workspace = true +tower.workspace = true +thiserror.workspace = true +jsonrpsee.workspace = true + +[dev-dependencies] +reth-payload-builder = { workspace = true, features = ["test-utils"] } +reth-engine-primitives.workspace = true +reth-payload-primitives.workspace = true +reth-primitives.workspace = true +reth-primitives-traits.workspace = true +reth-provider = { workspace = true, features = ["test-utils"] } +reth-rpc-builder.workspace = true +reth-rpc-engine-api.workspace = true +reth-scroll-engine-primitives.workspace = true +reth-scroll-node.workspace = true +reth-scroll-payload = { workspace = true, features = ["test-utils"] } +reth-scroll-chainspec.workspace = true +reth-tasks.workspace = true +reth-tracing.workspace = true +reth-transaction-pool.workspace = true + +tokio = { workspace = true, features = ["rt", "rt-multi-thread"] } +futures-util.workspace = true + +[features] +default = ["std"] +std = [ + "alloy-primitives/std", + "alloy-rpc-types-engine/std", + "scroll-alloy-rpc-types-engine/std", + "derive_more/std", + "reth-engine-primitives/std", + "reth-primitives/std", + "reth-primitives-traits/std", + "futures-util/std", + "reth-scroll-chainspec/std", + "thiserror/std", + "scroll-alloy-network/std", +]
diff --git reth/crates/scroll/alloy/provider/src/engine/client.rs scroll-reth/crates/scroll/alloy/provider/src/engine/client.rs new file mode 100644 index 0000000000000000000000000000000000000000..145b833815c7bdd4ab9874f7dcdfaf461218375c --- /dev/null +++ scroll-reth/crates/scroll/alloy/provider/src/engine/client.rs @@ -0,0 +1,79 @@ +use super::{ScrollEngineApi, ScrollEngineApiResult}; +use alloy_primitives::{BlockHash, U64}; +use alloy_rpc_types_engine::{ + ClientVersionV1, ExecutionPayloadBodiesV1, ExecutionPayloadV1, ForkchoiceState, + ForkchoiceUpdated, PayloadId, PayloadStatus, +}; + +use reth_rpc_api::EngineApiClient; +use reth_scroll_engine_primitives::ScrollEngineTypes; +use scroll_alloy_rpc_types_engine::ScrollPayloadAttributes; + +/// A Client for a type that implements the [`EngineApiClient`] trait. +#[derive(Debug)] +pub struct ScrollAuthApiEngineClient<T> { + client: T, +} + +impl<T> ScrollAuthApiEngineClient<T> { + /// Creates a new [`ScrollAuthApiEngineClient`] with the given client. + pub const fn new(client: T) -> Self { + Self { client } + } +} + +#[async_trait::async_trait] +impl<EC: EngineApiClient<ScrollEngineTypes> + Sync> ScrollEngineApi + for ScrollAuthApiEngineClient<EC> +{ + async fn new_payload_v1( + &self, + payload: ExecutionPayloadV1, + ) -> ScrollEngineApiResult<PayloadStatus> { + Ok(self.client.new_payload_v1(payload).await?) + } + + async fn fork_choice_updated_v1( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option<ScrollPayloadAttributes>, + ) -> ScrollEngineApiResult<ForkchoiceUpdated> { + Ok(self.client.fork_choice_updated_v1(fork_choice_state, payload_attributes).await?) + } + + async fn get_payload_v1( + &self, + payload_id: PayloadId, + ) -> ScrollEngineApiResult<ExecutionPayloadV1> { + Ok(self.client.get_payload_v1(payload_id).await?) + } + + async fn get_payload_bodies_by_hash_v1( + &self, + block_hashes: Vec<BlockHash>, + ) -> ScrollEngineApiResult<ExecutionPayloadBodiesV1> { + Ok(self.client.get_payload_bodies_by_hash_v1(block_hashes).await?) + } + + async fn get_payload_bodies_by_range_v1( + &self, + start: U64, + count: U64, + ) -> ScrollEngineApiResult<ExecutionPayloadBodiesV1> { + Ok(self.client.get_payload_bodies_by_range_v1(start, count).await?) + } + + async fn get_client_version_v1( + &self, + client_version: ClientVersionV1, + ) -> ScrollEngineApiResult<Vec<ClientVersionV1>> { + Ok(self.client.get_client_version_v1(client_version).await?) + } + + async fn exchange_capabilities( + &self, + capabilities: Vec<String>, + ) -> ScrollEngineApiResult<Vec<String>> { + Ok(self.exchange_capabilities(capabilities).await?) + } +}
diff --git reth/crates/scroll/alloy/provider/src/engine/mod.rs scroll-reth/crates/scroll/alloy/provider/src/engine/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..1edfbefbfc219eee7c7fce09959f6701f522d192 --- /dev/null +++ scroll-reth/crates/scroll/alloy/provider/src/engine/mod.rs @@ -0,0 +1,104 @@ +mod provider; + +pub use provider::ScrollAuthEngineApiProvider; + +mod client; +pub use client::ScrollAuthApiEngineClient; + +use super::error::ScrollEngineApiError; +use alloy_primitives::{BlockHash, U64}; +use alloy_rpc_types_engine::{ + ClientVersionV1, ExecutionPayloadBodiesV1, ExecutionPayloadV1, ForkchoiceState, + ForkchoiceUpdated, PayloadId, PayloadStatus, +}; + +use scroll_alloy_rpc_types_engine::ScrollPayloadAttributes; + +/// A type alias for the result of the Scroll Engine API methods. +pub type ScrollEngineApiResult<T> = Result<T, ScrollEngineApiError>; + +/// Engine API trait for Scroll. Only exposes versions of the API that are supported. +/// Note: +/// > The provider should use a JWT authentication layer. +#[async_trait::async_trait] +pub trait ScrollEngineApi { + /// See also <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/paris.md#engine_newpayloadv1> + /// Caution: This should not accept the `withdrawals` field + async fn new_payload_v1( + &self, + payload: ExecutionPayloadV1, + ) -> ScrollEngineApiResult<PayloadStatus>; + + /// See also <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/paris.md#engine_forkchoiceupdatedv1> + /// Caution: This should not accept the `withdrawals` field in the payload attributes. + /// + /// Modifications: + /// - Adds the below fields to the `payload_attributes`: + /// - transactions: an optional list of transactions to include at the start of the block. + /// - `no_tx_pool`: a boolean which signals whether pool transactions need to be included in + /// the payload building task. + async fn fork_choice_updated_v1( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option<ScrollPayloadAttributes>, + ) -> ScrollEngineApiResult<ForkchoiceUpdated>; + + /// See also <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/paris.md#engine_getpayloadv1> + /// + /// Returns the most recent version of the payload that is available in the corresponding + /// payload build process at the time of receiving this call. + /// + /// Caution: This should not return the `withdrawals` field + /// + /// Note: + /// > Provider software MAY stop the corresponding build process after serving this call. + async fn get_payload_v1( + &self, + payload_id: PayloadId, + ) -> ScrollEngineApiResult<ExecutionPayloadV1>; + + /// See also <https://github.com/ethereum/execution-apis/blob/6452a6b194d7db269bf1dbd087a267251d3cc7f8/src/engine/shanghai.md#engine_getpayloadbodiesbyhashv1> + async fn get_payload_bodies_by_hash_v1( + &self, + block_hashes: Vec<BlockHash>, + ) -> ScrollEngineApiResult<ExecutionPayloadBodiesV1>; + + /// See also <https://github.com/ethereum/execution-apis/blob/6452a6b194d7db269bf1dbd087a267251d3cc7f8/src/engine/shanghai.md#engine_getpayloadbodiesbyrangev1> + /// + /// Returns the execution payload bodies by the range starting at `start`, containing `count` + /// blocks. + /// + /// WARNING: This method is associated with the `BeaconBlocksByRange` message in the consensus + /// layer p2p specification, meaning the input should be treated as untrusted or potentially + /// adversarial. + /// + /// Implementers should take care when acting on the input to this method, specifically + /// ensuring that the range is limited properly, and that the range boundaries are computed + /// correctly and without panics. + async fn get_payload_bodies_by_range_v1( + &self, + start: U64, + count: U64, + ) -> ScrollEngineApiResult<ExecutionPayloadBodiesV1>; + + /// This function will return the `ClientVersionV1` object. + /// See also: + /// <https://github.com/ethereum/execution-apis/blob/03911ffc053b8b806123f1fc237184b0092a485a/src/engine/identification.md#engine_getclientversionv1>make fmt + /// + /// + /// - When connected to a single execution client, the consensus client **MUST** receive an + /// array with a single `ClientVersionV1` object. + /// - When connected to multiple execution clients via a multiplexer, the multiplexer **MUST** + /// concatenate the responses from each execution client into a single, + /// flat array before returning the response to the consensus client. + async fn get_client_version_v1( + &self, + client_version: ClientVersionV1, + ) -> ScrollEngineApiResult<Vec<ClientVersionV1>>; + + /// See also <https://github.com/ethereum/execution-apis/blob/6452a6b194d7db269bf1dbd087a267251d3cc7f8/src/engine/common.md#capabilities> + async fn exchange_capabilities( + &self, + capabilities: Vec<String>, + ) -> ScrollEngineApiResult<Vec<String>>; +}
diff --git reth/crates/scroll/alloy/provider/src/engine/provider.rs scroll-reth/crates/scroll/alloy/provider/src/engine/provider.rs new file mode 100644 index 0000000000000000000000000000000000000000..ade3de86edb6dee8464880e46da72ef149b0201a --- /dev/null +++ scroll-reth/crates/scroll/alloy/provider/src/engine/provider.rs @@ -0,0 +1,218 @@ +use super::{ScrollEngineApi, ScrollEngineApiResult}; +use alloy_primitives::{bytes::Bytes, BlockHash, U64}; +use alloy_provider::{Provider, RootProvider}; +use alloy_rpc_client::RpcClient; +use alloy_rpc_types_engine::{ + ClientVersionV1, ExecutionPayloadBodiesV1, ExecutionPayloadV1, ForkchoiceState, + ForkchoiceUpdated, JwtSecret, PayloadId, PayloadStatus, +}; +use alloy_transport::utils::guess_local_url; +use alloy_transport_http::{ + hyper_util, hyper_util::rt::TokioExecutor, AuthLayer, Http, HyperClient, +}; +use http_body_util::Full; +use reqwest::Url; +use scroll_alloy_network::Scroll; +use scroll_alloy_rpc_types_engine::ScrollPayloadAttributes; +use std::fmt::Debug; + +/// An authenticated [`alloy_provider::Provider`] to the [`ScrollEngineApi`]. +#[derive(Clone)] +pub struct ScrollAuthEngineApiProvider<P> { + provider: P, +} + +impl<P> Debug for ScrollAuthEngineApiProvider<P> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ScrollAuthEngineApiProvider").field("provider", &"provider").finish() + } +} + +impl ScrollAuthEngineApiProvider<RootProvider<Scroll>> { + /// Returns a new [`ScrollAuthEngineApiProvider`], authenticated for interfacing with the Engine + /// API server at the provided URL using the passed JWT secret. + pub fn new(jwt_secret: JwtSecret, url: Url) -> Self { + let auth_layer = AuthLayer::new(jwt_secret); + let hyper_client = hyper_util::client::legacy::Client::builder(TokioExecutor::new()) + .build_http::<Full<Bytes>>(); + + let service = tower::ServiceBuilder::new().layer(auth_layer).service(hyper_client); + let transport = HyperClient::<Full<Bytes>, _>::with_service(service); + + let is_url_local = guess_local_url(&url); + let http = Http::with_client(transport, url); + let client = RpcClient::new(http, is_url_local); + + let provider = RootProvider::<Scroll>::new(client); + Self { provider } + } +} + +impl<P> ScrollAuthEngineApiProvider<P> { + /// Returns a new [`ScrollAuthEngineApiProvider`] from the given provider. + pub const fn from_provider(provider: P) -> Self { + Self { provider } + } +} + +#[async_trait::async_trait] +impl<P: Provider<Scroll>> ScrollEngineApi for ScrollAuthEngineApiProvider<P> { + async fn new_payload_v1( + &self, + payload: ExecutionPayloadV1, + ) -> ScrollEngineApiResult<PayloadStatus> { + Ok(self.provider.client().request("engine_newPayloadV1", (payload,)).await?) + } + + async fn fork_choice_updated_v1( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option<ScrollPayloadAttributes>, + ) -> ScrollEngineApiResult<ForkchoiceUpdated> { + Ok(self + .provider + .client() + .request("engine_forkchoiceUpdatedV1", (fork_choice_state, payload_attributes)) + .await?) + } + + async fn get_payload_v1( + &self, + payload_id: PayloadId, + ) -> ScrollEngineApiResult<ExecutionPayloadV1> { + Ok(self.provider.client().request("engine_getPayloadV1", (payload_id,)).await?) + } + + async fn get_payload_bodies_by_hash_v1( + &self, + block_hashes: Vec<BlockHash>, + ) -> ScrollEngineApiResult<ExecutionPayloadBodiesV1> { + Ok(self + .provider + .client() + .request("engine_getPayloadBodiesByHashV1", (block_hashes,)) + .await?) + } + + async fn get_payload_bodies_by_range_v1( + &self, + start: U64, + count: U64, + ) -> ScrollEngineApiResult<ExecutionPayloadBodiesV1> { + Ok(self + .provider + .client() + .request("engine_getPayloadBodiesByRangeV1", (start, count)) + .await?) + } + + async fn get_client_version_v1( + &self, + client_version: ClientVersionV1, + ) -> ScrollEngineApiResult<Vec<ClientVersionV1>> { + Ok(self.provider.client().request("engine_getClientVersionV1", (client_version,)).await?) + } + + async fn exchange_capabilities( + &self, + capabilities: Vec<String>, + ) -> ScrollEngineApiResult<Vec<String>> { + Ok(self.provider.client().request("engine_exchangeCapabilities", (capabilities,)).await?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::engine::ScrollEngineApi; + use alloy_primitives::U64; + use alloy_rpc_types_engine::{ + ClientCode, ClientVersionV1, ExecutionPayloadV1, ForkchoiceState, PayloadId, + }; + use reth_engine_primitives::BeaconConsensusEngineHandle; + use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; + use reth_payload_primitives::PayloadTypes; + use reth_primitives::{Block, TransactionSigned}; + use reth_primitives_traits::block::Block as _; + use reth_provider::{test_utils::NoopProvider, CanonStateNotification}; + use reth_rpc_builder::auth::{AuthRpcModule, AuthServerConfig, AuthServerHandle}; + use reth_rpc_engine_api::{capabilities::EngineCapabilities, EngineApi}; + use reth_scroll_chainspec::SCROLL_MAINNET; + use reth_scroll_engine_primitives::{ + ScrollBuiltPayload, ScrollEngineTypes, ScrollPayloadBuilderAttributes, + }; + use reth_scroll_node::ScrollEngineValidator; + use reth_scroll_payload::NoopPayloadJobGenerator; + use reth_tasks::TokioTaskExecutor; + use reth_transaction_pool::noop::NoopTransactionPool; + use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; + use tokio::sync::mpsc::unbounded_channel; + + fn spawn_test_payload_service<T>() -> PayloadBuilderHandle<T> + where + T: PayloadTypes< + PayloadBuilderAttributes = ScrollPayloadBuilderAttributes, + BuiltPayload = ScrollBuiltPayload, + > + 'static, + { + let (service, handle) = PayloadBuilderService::< + NoopPayloadJobGenerator<ScrollPayloadBuilderAttributes, ScrollBuiltPayload>, + futures_util::stream::Empty<CanonStateNotification>, + T, + >::new(Default::default(), futures_util::stream::empty()); + tokio::spawn(service); + handle + } + + async fn launch_auth(jwt_secret: JwtSecret) -> AuthServerHandle { + let config = AuthServerConfig::builder(jwt_secret) + .socket_addr(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0))) + .build(); + let (tx, _rx) = unbounded_channel(); + let beacon_engine_handle = BeaconConsensusEngineHandle::<ScrollEngineTypes>::new(tx); + let client = ClientVersionV1 { + code: ClientCode::RH, + name: "Reth".to_string(), + version: "v0.2.0-beta.5".to_string(), + commit: "defa64b2".to_string(), + }; + + let engine_api = EngineApi::new( + NoopProvider::default(), + SCROLL_MAINNET.clone(), + beacon_engine_handle, + spawn_test_payload_service().into(), + NoopTransactionPool::default(), + Box::<TokioTaskExecutor>::default(), + client, + EngineCapabilities::default(), + ScrollEngineValidator::new(SCROLL_MAINNET.clone()), + false, + ); + let module = AuthRpcModule::new(engine_api); + module.start_server(config).await.unwrap() + } + + #[allow(unused_must_use)] + #[tokio::test(flavor = "multi_thread")] + async fn test_engine_api_provider() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + let secret = JwtSecret::random(); + let handle = launch_auth(secret).await; + let url = handle.http_url().parse()?; + let provider = ScrollAuthEngineApiProvider::new(secret, url); + + let block = Block::<TransactionSigned>::default().seal_slow(); + let execution_payload = + ExecutionPayloadV1::from_block_unchecked(block.hash(), &block.clone().into_block()); + provider.new_payload_v1(execution_payload).await; + provider.fork_choice_updated_v1(ForkchoiceState::default(), None).await; + provider.get_payload_v1(PayloadId::new([0, 0, 0, 0, 0, 0, 0, 0])).await; + provider.get_payload_bodies_by_hash_v1(vec![]).await; + provider.get_payload_bodies_by_range_v1(U64::ZERO, U64::from(1u64)).await; + provider.exchange_capabilities(vec![]).await; + + Ok(()) + } +}
diff --git reth/crates/scroll/alloy/provider/src/error.rs scroll-reth/crates/scroll/alloy/provider/src/error.rs new file mode 100644 index 0000000000000000000000000000000000000000..88cfc1eb7af1b9a04c63935492e3d0bed790c3d5 --- /dev/null +++ scroll-reth/crates/scroll/alloy/provider/src/error.rs @@ -0,0 +1,10 @@ +/// The error type for the Scroll engine API. +#[derive(thiserror::Error, Debug)] +pub enum ScrollEngineApiError { + /// Error when decoding a response from an rpsee client. + #[error("Jsonrpsee error: {0}")] + JsonRpseeError(#[from] jsonrpsee::core::ClientError), + /// Error when decoding a response from an alloy client. + #[error("Alloy error: {0}")] + AlloyError(#[from] alloy_transport::TransportError), +}
diff --git reth/crates/scroll/alloy/provider/src/lib.rs scroll-reth/crates/scroll/alloy/provider/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..812b635562098ab12bbead82f1a5a6bd1a1646f9 --- /dev/null +++ scroll-reth/crates/scroll/alloy/provider/src/lib.rs @@ -0,0 +1,9 @@ +//! Providers implementations fitted to Scroll needs. + +mod engine; +pub use engine::{ + ScrollAuthApiEngineClient, ScrollAuthEngineApiProvider, ScrollEngineApi, ScrollEngineApiResult, +}; + +mod error; +pub use error::ScrollEngineApiError;
diff --git reth/crates/scroll/alloy/rpc-types-engine/Cargo.toml scroll-reth/crates/scroll/alloy/rpc-types-engine/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..41dd51a6d0921a79e818a804fae2135614498d96 --- /dev/null +++ scroll-reth/crates/scroll/alloy/rpc-types-engine/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "scroll-alloy-rpc-types-engine" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# alloy +alloy-primitives.workspace = true +alloy-rpc-types-engine.workspace = true + +# misc +serde = { workspace = true, optional = true } + +# test-utils +arbitrary = { workspace = true, optional = true } + +[dev-dependencies] +alloy-primitives = { workspace = true, features = ["getrandom"] } +serde_json.workspace = true + +[features] +default = ["std"] +arbitrary = [ + "dep:arbitrary", + "alloy-primitives/arbitrary", + "alloy-rpc-types-engine/arbitrary", +] +std = [ + "alloy-primitives/std", + "alloy-rpc-types-engine/std", + "serde?/std", + "serde_json/std", +] +serde = [ + "dep:serde", + "alloy-primitives/serde", + "alloy-rpc-types-engine/serde", +]
diff --git reth/crates/scroll/alloy/rpc-types-engine/src/attributes.rs scroll-reth/crates/scroll/alloy/rpc-types-engine/src/attributes.rs new file mode 100644 index 0000000000000000000000000000000000000000..25b522ac0315680c385e5e6be7cc7b1c1b2ab0cf --- /dev/null +++ scroll-reth/crates/scroll/alloy/rpc-types-engine/src/attributes.rs @@ -0,0 +1,123 @@ +//! Scroll-specific payload attributes. + +use alloc::vec::Vec; +use alloy_primitives::{Address, Bytes, B256, U256}; +use alloy_rpc_types_engine::PayloadAttributes; + +/// The payload attributes for block building tailored for Scroll. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct ScrollPayloadAttributes { + /// The payload attributes. + pub payload_attributes: PayloadAttributes, + /// An optional array of transaction to be forced included in the block (includes l1 messages). + pub transactions: Option<Vec<Bytes>>, + /// Indicates whether the payload building job should happen with or without pool transactions. + pub no_tx_pool: bool, + /// The block data hint, used pre-Euclid by the block builder to derive the correct block + /// hash and post-Euclid by the sequencer to set the difficulty of the block. + pub block_data_hint: BlockDataHint, + /// The gas limit for the block building task. + pub gas_limit: Option<u64>, +} + +/// Block data provided as a hint to the payload attributes. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct BlockDataHint { + /// The optional extra data for the block. + pub extra_data: Option<Bytes>, + /// The optional state root for the block. + pub state_root: Option<B256>, + /// The optional coinbase for the block. + pub coinbase: Option<Address>, + /// The optional nonce for the block. + pub nonce: Option<u64>, + /// The optional difficulty for the block. + pub difficulty: Option<U256>, +} + +impl BlockDataHint { + /// Returns an empty [`BlockDataHint`] with all fields set to `None`. + pub fn none() -> Self { + Self::default() + } + + /// Returns `true` if the [`BlockDataHint`] is empty. + pub const fn is_empty(&self) -> bool { + self.extra_data.is_none() && + self.state_root.is_none() && + self.coinbase.is_none() && + self.nonce.is_none() && + self.difficulty.is_none() + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for BlockDataHint { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> { + Ok(Self { + extra_data: Some(Bytes::arbitrary(u)?), + state_root: Some(B256::arbitrary(u)?), + coinbase: Some(Address::arbitrary(u)?), + nonce: Some(u64::arbitrary(u)?), + difficulty: Some(U256::arbitrary(u)?), + }) + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for ScrollPayloadAttributes { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> { + Ok(Self { + payload_attributes: PayloadAttributes { + timestamp: u64::arbitrary(u)?, + prev_randao: alloy_primitives::B256::arbitrary(u)?, + suggested_fee_recipient: alloy_primitives::Address::arbitrary(u)?, + withdrawals: None, + parent_beacon_block_root: Some(alloy_primitives::B256::arbitrary(u)?), + }, + transactions: Some(Vec::arbitrary(u)?), + no_tx_pool: bool::arbitrary(u)?, + block_data_hint: BlockDataHint::arbitrary(u)?, + gas_limit: Some(u64::arbitrary(u)?), + }) + } +} + +#[cfg(all(test, feature = "serde"))] +mod test { + use super::*; + use alloy_primitives::{Address, B256}; + use alloy_rpc_types_engine::PayloadAttributes; + + #[test] + fn test_serde_roundtrip_attributes() { + let attributes = ScrollPayloadAttributes { + payload_attributes: PayloadAttributes { + timestamp: 0x1337, + prev_randao: B256::ZERO, + suggested_fee_recipient: Address::ZERO, + withdrawals: Default::default(), + parent_beacon_block_root: Some(B256::ZERO), + }, + transactions: Some(vec![b"hello".to_vec().into()]), + no_tx_pool: true, + block_data_hint: BlockDataHint { + extra_data: Some(b"world".into()), + state_root: Some(B256::random()), + coinbase: Some(Address::random()), + nonce: Some(0x12345), + difficulty: Some(U256::from(10)), + }, + gas_limit: Some(10_000_000), + }; + + let ser = serde_json::to_string(&attributes).unwrap(); + let de: ScrollPayloadAttributes = serde_json::from_str(&ser).unwrap(); + + assert_eq!(attributes, de); + } +}
diff --git reth/crates/scroll/alloy/rpc-types-engine/src/lib.rs scroll-reth/crates/scroll/alloy/rpc-types-engine/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..fcf62c269351c5e3dfe1ffa10ac66cc5fff95f6d --- /dev/null +++ scroll-reth/crates/scroll/alloy/rpc-types-engine/src/lib.rs @@ -0,0 +1,8 @@ +//! Scroll types for interaction with the Engine API via RPC. + +#![cfg_attr(not(feature = "std"), no_std)] + +mod attributes; +pub use attributes::{BlockDataHint, ScrollPayloadAttributes}; + +extern crate alloc;
diff --git reth/crates/scroll/alloy/rpc-types/Cargo.toml scroll-reth/crates/scroll/alloy/rpc-types/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..ceb402689dfbbedaf1f23ad06d36d309ff1916af --- /dev/null +++ scroll-reth/crates/scroll/alloy/rpc-types/Cargo.toml @@ -0,0 +1,61 @@ +[package] +name = "scroll-alloy-rpc-types" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +scroll-alloy-consensus = { workspace = true, features = ["serde"] } +alloy-consensus = { workspace = true, default-features = false } +alloy-eips = { workspace = true, features = ["serde"], default-features = false } +alloy-network-primitives = { workspace = true, default-features = false } +alloy-primitives = { workspace = true, features = ["map", "rlp", "serde"], default-features = false } +alloy-rpc-types-eth = { workspace = true, features = ["serde"], default-features = false } +alloy-serde = { workspace = true, default-features = false } +arbitrary = { workspace = true, features = ["derive", "derive"], optional = true } +derive_more = { workspace = true, default-features = false } +serde = { workspace = true, features = ["derive", "alloc", "derive"], default-features = false } +serde_json = { workspace = true, features = ["alloc"], default-features = false } + +[dev-dependencies] +similar-asserts = "1.6" +alloy-consensus = { workspace = true, features = ["arbitrary"], default-features = false } +alloy-primitives = { workspace = true, features = ["arbitrary"], default-features = false } +alloy-rpc-types-eth = { workspace = true, features = ["arbitrary"], default-features = false } +arbitrary = { workspace = true, features = ["derive", "derive"] } + +[features] +arbitrary = [ + "std", + "dep:arbitrary", + "alloy-primitives/arbitrary", + "alloy-rpc-types-eth/arbitrary", + "scroll-alloy-consensus/arbitrary", + "alloy-consensus/arbitrary", + "alloy-eips/arbitrary", + "alloy-serde/arbitrary", +] +default = ["std"] +k256 = [ + "alloy-rpc-types-eth/k256", + "scroll-alloy-consensus/k256", +] +std = [ + "alloy-network-primitives/std", + "alloy-eips/std", + "alloy-primitives/std", + "alloy-rpc-types-eth/std", + "alloy-consensus/std", + "alloy-serde/std", + "derive_more/std", + "serde/std", + "serde_json/std", + "scroll-alloy-consensus/std", +]
diff --git reth/crates/scroll/alloy/rpc-types/README.md scroll-reth/crates/scroll/alloy/rpc-types/README.md new file mode 100644 index 0000000000000000000000000000000000000000..963a99c4b23daddceeb49feb55a579509ed91ac1 --- /dev/null +++ scroll-reth/crates/scroll/alloy/rpc-types/README.md @@ -0,0 +1,3 @@ +# scroll-alloy-rpc-types + +Scroll RPC-related types.
diff --git reth/crates/scroll/alloy/rpc-types/src/lib.rs scroll-reth/crates/scroll/alloy/rpc-types/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..3236795f79ae6fa5ee6b9ffba84a4806cd38af6d --- /dev/null +++ scroll-reth/crates/scroll/alloy/rpc-types/src/lib.rs @@ -0,0 +1,14 @@ +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/alloy.jpg", + html_favicon_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/favicon.ico" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(any(test, feature = "std")), no_std)] + +mod receipt; +pub use receipt::{ScrollTransactionReceipt, ScrollTransactionReceiptFields}; + +mod transaction; +pub use transaction::{ScrollL1MessageTransactionFields, ScrollTransactionRequest, Transaction};
diff --git reth/crates/scroll/alloy/rpc-types/src/receipt.rs scroll-reth/crates/scroll/alloy/rpc-types/src/receipt.rs new file mode 100644 index 0000000000000000000000000000000000000000..6a0a04c660252c0180e7940015f49d6e7d2b0c3c --- /dev/null +++ scroll-reth/crates/scroll/alloy/rpc-types/src/receipt.rs @@ -0,0 +1,176 @@ +//! Receipt types for RPC + +use alloy_consensus::{Receipt, ReceiptWithBloom}; +use alloy_serde::OtherFields; +use serde::{Deserialize, Serialize}; + +use scroll_alloy_consensus::ScrollReceiptEnvelope; + +/// Scroll Transaction Receipt type +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[doc(alias = "ScrollTxReceipt")] +pub struct ScrollTransactionReceipt { + /// Regular eth transaction receipt including deposit receipts + #[serde(flatten)] + pub inner: + alloy_rpc_types_eth::TransactionReceipt<ScrollReceiptEnvelope<alloy_rpc_types_eth::Log>>, + /// L1 fee for the transaction. + #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + pub l1_fee: Option<u128>, +} + +impl alloy_network_primitives::ReceiptResponse for ScrollTransactionReceipt { + fn contract_address(&self) -> Option<alloy_primitives::Address> { + self.inner.contract_address + } + + fn status(&self) -> bool { + self.inner.inner.status() + } + + fn block_hash(&self) -> Option<alloy_primitives::BlockHash> { + self.inner.block_hash + } + + fn block_number(&self) -> Option<u64> { + self.inner.block_number + } + + fn transaction_hash(&self) -> alloy_primitives::TxHash { + self.inner.transaction_hash + } + + fn transaction_index(&self) -> Option<u64> { + self.inner.transaction_index() + } + + fn gas_used(&self) -> u64 { + self.inner.gas_used() + } + + fn effective_gas_price(&self) -> u128 { + self.inner.effective_gas_price() + } + + fn blob_gas_used(&self) -> Option<u64> { + self.inner.blob_gas_used() + } + + fn blob_gas_price(&self) -> Option<u128> { + self.inner.blob_gas_price() + } + + fn from(&self) -> alloy_primitives::Address { + self.inner.from() + } + + fn to(&self) -> Option<alloy_primitives::Address> { + self.inner.to() + } + + fn cumulative_gas_used(&self) -> u64 { + self.inner.cumulative_gas_used() + } + + fn state_root(&self) -> Option<alloy_primitives::B256> { + self.inner.state_root() + } +} + +/// Additional fields for Scroll transaction receipts: <https://github.com/scroll-tech/go-ethereum/blob/develop/core/types/receipt.go#L78> +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[doc(alias = "ScrollTxReceiptFields")] +pub struct ScrollTransactionReceiptFields { + /// L1 fee for the transaction. + #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + pub l1_fee: Option<u128>, +} + +impl From<ScrollTransactionReceiptFields> for OtherFields { + fn from(value: ScrollTransactionReceiptFields) -> Self { + serde_json::to_value(value).unwrap().try_into().unwrap() + } +} + +impl From<ScrollTransactionReceipt> for ScrollReceiptEnvelope<alloy_primitives::Log> { + fn from(value: ScrollTransactionReceipt) -> Self { + let inner_envelope = value.inner.inner; + + /// Helper function to convert the inner logs within a [`ReceiptWithBloom`] from RPC to + /// consensus types. + #[inline(always)] + fn convert_standard_receipt( + receipt: ReceiptWithBloom<Receipt<alloy_rpc_types_eth::Log>>, + ) -> ReceiptWithBloom<Receipt<alloy_primitives::Log>> { + let ReceiptWithBloom { logs_bloom, receipt } = receipt; + + let consensus_logs = receipt.logs.into_iter().map(|log| log.inner).collect(); + ReceiptWithBloom { + receipt: Receipt { + status: receipt.status, + cumulative_gas_used: receipt.cumulative_gas_used, + logs: consensus_logs, + }, + logs_bloom, + } + } + + match inner_envelope { + ScrollReceiptEnvelope::Legacy(receipt) => { + Self::Legacy(convert_standard_receipt(receipt)) + } + ScrollReceiptEnvelope::Eip2930(receipt) => { + Self::Eip2930(convert_standard_receipt(receipt)) + } + ScrollReceiptEnvelope::Eip1559(receipt) => { + Self::Eip1559(convert_standard_receipt(receipt)) + } + ScrollReceiptEnvelope::L1Message(receipt) => { + Self::L1Message(convert_standard_receipt(receipt)) + } + _ => unreachable!("Unsupported ScrollReceiptEnvelope variant"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + // <https://github.com/alloy-rs/op-alloy/issues/18> + #[test] + fn parse_rpc_receipt() { + let s = r#"{ + "blockHash": "0x9e6a0fb7e22159d943d760608cc36a0fb596d1ab3c997146f5b7c55c8c718c67", + "blockNumber": "0x6cfef89", + "contractAddress": null, + "cumulativeGasUsed": "0xfa0d", + "effectiveGasPrice": "0x0", + "from": "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001", + "gasUsed": "0xfa0d", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": "0x4200000000000000000000000000000000000015", + "transactionHash": "0xb7c74afdeb7c89fb9de2c312f49b38cb7a850ba36e064734c5223a477e83fdc9", + "transactionIndex": "0x0", + "type": "0x7e" + }"#; + + let receipt: ScrollTransactionReceipt = serde_json::from_str(s).unwrap(); + let value = serde_json::to_value(&receipt).unwrap(); + let expected_value = serde_json::from_str::<serde_json::Value>(s).unwrap(); + assert_eq!(value, expected_value); + } + + #[test] + fn serialize_empty_scroll_transaction_receipt_fields_struct() { + let scroll_fields = ScrollTransactionReceiptFields::default(); + + let json = serde_json::to_value(scroll_fields).unwrap(); + assert_eq!(json, json!({})); + } +}
diff --git reth/crates/scroll/alloy/rpc-types/src/transaction.rs scroll-reth/crates/scroll/alloy/rpc-types/src/transaction.rs new file mode 100644 index 0000000000000000000000000000000000000000..d4c482d3cd4040f69d32ef6654bc64319c42482e --- /dev/null +++ scroll-reth/crates/scroll/alloy/rpc-types/src/transaction.rs @@ -0,0 +1,317 @@ +//! Scroll specific types related to transactions. + +use alloy_consensus::{transaction::Recovered, Transaction as _, Typed2718}; +use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization}; +use alloy_primitives::{Address, BlockHash, Bytes, ChainId, TxKind, B256, U256}; +use alloy_serde::OtherFields; +use scroll_alloy_consensus::{ScrollTransactionInfo, ScrollTxEnvelope}; +use serde::{Deserialize, Serialize}; + +mod request; +pub use request::ScrollTransactionRequest; + +/// Scroll Transaction type +#[derive( + Clone, Debug, PartialEq, Eq, Serialize, Deserialize, derive_more::Deref, derive_more::DerefMut, +)] +#[serde(try_from = "tx_serde::TransactionSerdeHelper", into = "tx_serde::TransactionSerdeHelper")] +#[cfg_attr(all(any(test, feature = "arbitrary"), feature = "k256"), derive(arbitrary::Arbitrary))] +pub struct Transaction { + /// Ethereum Transaction Types + #[deref] + #[deref_mut] + pub inner: alloy_rpc_types_eth::Transaction<ScrollTxEnvelope>, +} + +impl Transaction { + /// Returns a rpc [`Transaction`] with a [`ScrollTransactionInfo`] and + /// [`Recovered<ScrollTxEnvelope>`] as input. + pub fn from_transaction( + tx: Recovered<ScrollTxEnvelope>, + tx_info: ScrollTransactionInfo, + ) -> Self { + let base_fee = tx_info.inner.base_fee; + let effective_gas_price = if tx.is_l1_message() { + // For l1 messages, we set the `gasPrice` field to 0 in rpc + 0 + } else { + // TODO: should we get the pool base fee in the case where the transaction is a pending + // transaction here? + base_fee + .map(|base_fee| { + tx.effective_tip_per_gas(base_fee).unwrap_or_default() + base_fee as u128 + }) + .unwrap_or_else(|| tx.max_fee_per_gas()) + }; + + Self { + inner: alloy_rpc_types_eth::Transaction { + inner: tx, + block_hash: tx_info.inner.block_hash, + block_number: tx_info.inner.block_number, + transaction_index: tx_info.inner.index, + effective_gas_price: Some(effective_gas_price), + }, + } + } +} + +impl Typed2718 for Transaction { + fn ty(&self) -> u8 { + self.inner.ty() + } +} + +impl alloy_consensus::Transaction for Transaction { + fn chain_id(&self) -> Option<ChainId> { + self.inner.chain_id() + } + + fn nonce(&self) -> u64 { + self.inner.nonce() + } + + fn gas_limit(&self) -> u64 { + self.inner.gas_limit() + } + + fn gas_price(&self) -> Option<u128> { + self.inner.gas_price() + } + + fn max_fee_per_gas(&self) -> u128 { + self.inner.max_fee_per_gas() + } + + fn max_priority_fee_per_gas(&self) -> Option<u128> { + self.inner.max_priority_fee_per_gas() + } + + fn max_fee_per_blob_gas(&self) -> Option<u128> { + self.inner.max_fee_per_blob_gas() + } + + fn priority_fee_or_price(&self) -> u128 { + self.inner.priority_fee_or_price() + } + + fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 { + self.inner.effective_gas_price(base_fee) + } + + fn is_dynamic_fee(&self) -> bool { + self.inner.is_dynamic_fee() + } + + fn kind(&self) -> TxKind { + self.inner.kind() + } + + fn is_create(&self) -> bool { + self.inner.is_create() + } + + fn to(&self) -> Option<Address> { + self.inner.to() + } + + fn value(&self) -> U256 { + self.inner.value() + } + + fn input(&self) -> &Bytes { + self.inner.input() + } + + fn access_list(&self) -> Option<&AccessList> { + self.inner.access_list() + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + self.inner.blob_versioned_hashes() + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + self.inner.authorization_list() + } +} + +impl alloy_network_primitives::TransactionResponse for Transaction { + fn tx_hash(&self) -> alloy_primitives::TxHash { + self.inner.tx_hash() + } + + fn block_hash(&self) -> Option<BlockHash> { + self.inner.block_hash() + } + + fn block_number(&self) -> Option<u64> { + self.inner.block_number() + } + + fn transaction_index(&self) -> Option<u64> { + self.inner.transaction_index() + } + + fn from(&self) -> Address { + self.inner.from() + } +} + +/// Scroll specific transaction fields +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScrollL1MessageTransactionFields { + /// The index of the transaction in the message queue. + pub queue_index: u64, + /// The sender of the transaction on the L1. + pub sender: Address, +} + +impl From<ScrollL1MessageTransactionFields> for OtherFields { + fn from(value: ScrollL1MessageTransactionFields) -> Self { + serde_json::to_value(value).unwrap().try_into().unwrap() + } +} + +impl AsRef<ScrollTxEnvelope> for Transaction { + fn as_ref(&self) -> &ScrollTxEnvelope { + self.inner.as_ref() + } +} + +mod tx_serde { + //! Helper module for serializing and deserializing Scroll [`Transaction`]. + //! + //! This is needed because we might need to deserialize the `from` field into both + //! [`alloy_rpc_types_eth::Transaction::from`] and + //! [`scroll_alloy_consensus::TxL1Message`]. + //! + //! Additionally, we need similar logic for the `gasPrice` field + + use super::*; + use alloy_consensus::transaction::Recovered; + use serde::de::Error; + + /// Helper struct which will be flattened into the transaction and will only contain `from` + /// field if inner [`ScrollTxEnvelope`] did not consume it. + #[derive(Serialize, Deserialize)] + struct OptionalFields { + #[serde(default, skip_serializing_if = "Option::is_none")] + from: Option<Address>, + #[serde( + default, + rename = "gasPrice", + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + )] + effective_gas_price: Option<u128>, + } + + #[derive(Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub(crate) struct TransactionSerdeHelper { + #[serde(flatten)] + inner: ScrollTxEnvelope, + #[serde(default)] + block_hash: Option<BlockHash>, + #[serde(default, with = "alloy_serde::quantity::opt")] + block_number: Option<u64>, + #[serde(default, with = "alloy_serde::quantity::opt")] + transaction_index: Option<u64>, + #[serde(flatten)] + other: OptionalFields, + } + + impl From<Transaction> for TransactionSerdeHelper { + fn from(value: Transaction) -> Self { + let Transaction { + inner: + alloy_rpc_types_eth::Transaction { + inner: recovered, + block_hash, + block_number, + transaction_index, + effective_gas_price, + }, + .. + } = value; + + // if inner transaction has its own `gasPrice` don't serialize it in this struct. + let effective_gas_price = + effective_gas_price.filter(|_| recovered.gas_price().is_none()); + let (inner, from) = recovered.into_parts(); + + Self { + inner, + block_hash, + block_number, + transaction_index, + other: OptionalFields { from: Some(from), effective_gas_price }, + } + } + } + + impl TryFrom<TransactionSerdeHelper> for Transaction { + type Error = serde_json::Error; + + fn try_from(value: TransactionSerdeHelper) -> Result<Self, Self::Error> { + let TransactionSerdeHelper { + inner, + block_hash, + block_number, + transaction_index, + other, + } = value; + + // Try to get `from` field from inner envelope or from `MaybeFrom`, otherwise return + // error + let from = if let Some(from) = other.from { + from + } else if let ScrollTxEnvelope::L1Message(tx) = &inner { + tx.sender + } else { + return Err(serde_json::Error::custom("missing `from` field")); + }; + + let effective_gas_price = other.effective_gas_price.or_else(|| inner.gas_price()); + let recovered = Recovered::new_unchecked(inner, from); + + Ok(Self { + inner: alloy_rpc_types_eth::Transaction { + inner: recovered, + block_hash, + block_number, + transaction_index, + effective_gas_price, + }, + }) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::address; + + #[test] + fn can_deserialize_deposit() { + // cast rpc eth_getTransactionByHash + // 0x5c1c3785c8bf5d7f1cb714abd1d22e32642887215602c3a14a5e9ee105bad6aa --rpc-url https://rpc.scroll.io + let rpc_tx = r#"{"blockHash":"0x018ed80ea8340984a1f4841490284d6e51d71f9e9411feeca41e007a89fbfdff","blockNumber":"0xb81121","from":"0x7885bcbd5cecef1336b5300fb5186a12ddd8c478","gas":"0x1e8480","gasPrice":"0x0","hash":"0x5c1c3785c8bf5d7f1cb714abd1d22e32642887215602c3a14a5e9ee105bad6aa","input":"0x8ef1332e000000000000000000000000c186fa914353c44b2e33ebe05f21846f1048beda0000000000000000000000003bad7ad0728f9917d1bf08af5782dcbd516cdd96000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e7ba000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044493a4f846ffc1507cbfe98a2b0ba1f06ea7e4eb749c001f78f6cb5540daa556a0566322a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","to":"0x781e90f1c8fc4611c9b7497c3b47f99ef6969cbc","transactionIndex":"0x0","value":"0x0","type":"0x7e","v":"0x0","r":"0x0","s":"0x0","sender":"0x7885bcbd5cecef1336b5300fb5186a12ddd8c478","queueIndex":"0xe7ba0", "yParity":"0x0"}"#; + + let tx = serde_json::from_str::<Transaction>(rpc_tx).unwrap(); + + let ScrollTxEnvelope::L1Message(inner) = tx.as_ref() else { + panic!("Expected deposit transaction"); + }; + assert_eq!(inner.sender, address!("7885bcbd5cecef1336b5300fb5186a12ddd8c478")); + assert_eq!(inner.queue_index, 0xe7ba0); + assert_eq!(tx.inner.effective_gas_price, Some(0)); + + let deserialized = serde_json::to_value(&tx).unwrap(); + let expected = serde_json::from_str::<serde_json::Value>(rpc_tx).unwrap(); + similar_asserts::assert_eq!(deserialized, expected); + } +}
diff --git reth/crates/scroll/alloy/rpc-types/src/transaction/request.rs scroll-reth/crates/scroll/alloy/rpc-types/src/transaction/request.rs new file mode 100644 index 0000000000000000000000000000000000000000..b8e1b9cc060e316485f247c996c5383b304e1561 --- /dev/null +++ scroll-reth/crates/scroll/alloy/rpc-types/src/transaction/request.rs @@ -0,0 +1,184 @@ +use alloy_consensus::{ + Sealed, SignableTransaction, Signed, TxEip1559, TxEip4844, TypedTransaction, +}; +use alloy_primitives::{Address, Signature, TxKind, U256}; +use alloy_rpc_types_eth::{AccessList, TransactionInput, TransactionRequest}; +use serde::{Deserialize, Serialize}; + +use scroll_alloy_consensus::{ScrollTxEnvelope, ScrollTypedTransaction, TxL1Message}; + +/// `ScrollTransactionRequest` is a wrapper around the `TransactionRequest` struct. +/// This struct derives several traits to facilitate easier use and manipulation +/// in the codebase. +#[derive( + Clone, + Debug, + Default, + PartialEq, + Eq, + Hash, + derive_more::From, + derive_more::AsRef, + derive_more::AsMut, + Serialize, + Deserialize, +)] +#[serde(transparent)] +pub struct ScrollTransactionRequest(TransactionRequest); + +impl ScrollTransactionRequest { + /// Sets the from field in the call to the provided address + #[inline] + pub const fn from(mut self, from: Address) -> Self { + self.0.from = Some(from); + self + } + + /// Sets the transactions type for the transactions. + #[doc(alias = "tx_type")] + pub const fn transaction_type(mut self, transaction_type: u8) -> Self { + self.0.transaction_type = Some(transaction_type); + self + } + + /// Sets the gas limit for the transaction. + pub const fn gas_limit(mut self, gas_limit: u64) -> Self { + self.0.gas = Some(gas_limit); + self + } + + /// Sets the nonce for the transaction. + pub const fn nonce(mut self, nonce: u64) -> Self { + self.0.nonce = Some(nonce); + self + } + + /// Sets the maximum fee per gas for the transaction. + pub const fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self { + self.0.max_fee_per_gas = Some(max_fee_per_gas); + self + } + + /// Sets the maximum priority fee per gas for the transaction. + pub const fn max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: u128) -> Self { + self.0.max_priority_fee_per_gas = Some(max_priority_fee_per_gas); + self + } + + /// Sets the recipient address for the transaction. + #[inline] + pub const fn to(mut self, to: Address) -> Self { + self.0.to = Some(TxKind::Call(to)); + self + } + + /// Sets the value (amount) for the transaction. + pub const fn value(mut self, value: U256) -> Self { + self.0.value = Some(value); + self + } + + /// Sets the access list for the transaction. + pub fn access_list(mut self, access_list: AccessList) -> Self { + self.0.access_list = Some(access_list); + self + } + + /// Sets the input data for the transaction. + pub fn input(mut self, input: TransactionInput) -> Self { + self.0.input = input; + self + } + + /// Builds [`ScrollTypedTransaction`] from this builder. See + /// [`TransactionRequest::build_typed_tx`] for more info. + /// + /// Note that EIP-4844 transactions are not supported by Scroll and will be converted into + /// EIP-1559 transactions. + pub fn build_typed_tx(self) -> Result<ScrollTypedTransaction, Self> { + let tx = self.0.build_typed_tx().map_err(Self)?; + match tx { + TypedTransaction::Legacy(tx) => Ok(ScrollTypedTransaction::Legacy(tx)), + TypedTransaction::Eip1559(tx) => Ok(ScrollTypedTransaction::Eip1559(tx)), + TypedTransaction::Eip2930(tx) => Ok(ScrollTypedTransaction::Eip2930(tx)), + TypedTransaction::Eip4844(tx) => { + let tx: TxEip4844 = tx.into(); + Ok(ScrollTypedTransaction::Eip1559(TxEip1559 { + chain_id: tx.chain_id, + nonce: tx.nonce, + gas_limit: tx.gas_limit, + max_priority_fee_per_gas: tx.max_priority_fee_per_gas, + max_fee_per_gas: tx.max_fee_per_gas, + to: TxKind::Call(tx.to), + value: tx.value, + access_list: tx.access_list, + input: tx.input, + })) + } + TypedTransaction::Eip7702(_) => { + unimplemented!("EIP-7702 support is not implemented yet") + } + } + } +} + +impl From<TxL1Message> for ScrollTransactionRequest { + fn from(tx_l1_message: TxL1Message) -> Self { + let to = TxKind::from(tx_l1_message.to); + Self(TransactionRequest { + from: Some(tx_l1_message.sender), + to: Some(to), + value: Some(tx_l1_message.value), + gas: Some(tx_l1_message.gas_limit), + input: tx_l1_message.input.into(), + ..Default::default() + }) + } +} + +impl From<Sealed<TxL1Message>> for ScrollTransactionRequest { + fn from(value: Sealed<TxL1Message>) -> Self { + value.into_inner().into() + } +} + +impl<T> From<Signed<T, Signature>> for ScrollTransactionRequest +where + T: SignableTransaction<Signature> + Into<TransactionRequest>, +{ + fn from(value: Signed<T, Signature>) -> Self { + #[cfg(feature = "k256")] + let from = value.recover_signer().ok(); + #[cfg(not(feature = "k256"))] + let from = None; + + let mut inner: TransactionRequest = value.strip_signature().into(); + inner.from = from; + + Self(inner) + } +} + +impl From<ScrollTypedTransaction> for ScrollTransactionRequest { + fn from(tx: ScrollTypedTransaction) -> Self { + match tx { + ScrollTypedTransaction::Legacy(tx) => Self(tx.into()), + ScrollTypedTransaction::Eip2930(tx) => Self(tx.into()), + ScrollTypedTransaction::Eip1559(tx) => Self(tx.into()), + ScrollTypedTransaction::Eip7702(tx) => Self(tx.into()), + ScrollTypedTransaction::L1Message(tx) => tx.into(), + } + } +} + +impl From<ScrollTxEnvelope> for ScrollTransactionRequest { + fn from(value: ScrollTxEnvelope) -> Self { + match value { + ScrollTxEnvelope::Eip2930(tx) => tx.into(), + ScrollTxEnvelope::Eip1559(tx) => tx.into(), + ScrollTxEnvelope::Eip7702(tx) => tx.into(), + ScrollTxEnvelope::L1Message(tx) => tx.into(), + _ => Default::default(), + } + } +}
diff --git reth/crates/scroll/bin/scroll-reth/Cargo.toml scroll-reth/crates/scroll/bin/scroll-reth/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..896eecf8f4e13d11c2dc5b864658f9ea442cac7d --- /dev/null +++ scroll-reth/crates/scroll/bin/scroll-reth/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "scroll-reth" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# reth +reth-cli-util.workspace = true + +# scroll +reth-scroll-cli.workspace = true +reth-scroll-node.workspace = true + +# misc +clap = { workspace = true, features = ["derive", "env"] } +tracing.workspace = true + +[features] +dev = ["reth-scroll-cli/dev"] + +[[bin]] +name = "scroll-reth" +path = "src/main.rs"
diff --git reth/crates/scroll/bin/scroll-reth/src/main.rs scroll-reth/crates/scroll/bin/scroll-reth/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..97daff973af9ec79ec927ee523e9f04346f5cc49 --- /dev/null +++ scroll-reth/crates/scroll/bin/scroll-reth/src/main.rs @@ -0,0 +1,30 @@ +//! Scroll binary + +#[global_allocator] +static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); + +fn main() { + use clap::Parser; + use reth_scroll_cli::{Cli, ScrollChainSpecParser}; + use reth_scroll_node::{ScrollNode, ScrollRollupArgs}; + use tracing::info; + + reth_cli_util::sigsegv_handler::install(); + + // Enable backtraces unless a RUST_BACKTRACE value has already been explicitly provided. + if std::env::var_os("RUST_BACKTRACE").is_none() { + std::env::set_var("RUST_BACKTRACE", "1"); + } + + if let Err(err) = + Cli::<ScrollChainSpecParser, ScrollRollupArgs>::parse().run(|builder, args| async move { + info!(target: "reth::cli", "Launching node"); + let handle = + builder.node(ScrollNode::new(args)).launch_with_debug_capabilities().await?; + handle.node_exit_future.await + }) + { + eprintln!("Error: {err:?}"); + std::process::exit(1); + } +}
diff --git reth/crates/scroll/chainspec/Cargo.toml scroll-reth/crates/scroll/chainspec/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..60298bef3e021cd099922ddb579313d11f8c99e0 --- /dev/null +++ scroll-reth/crates/scroll/chainspec/Cargo.toml @@ -0,0 +1,67 @@ +[package] +name = "reth-scroll-chainspec" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "EVM chain spec implementation for scroll." + +[lints] +workspace = true + +[dependencies] +# reth +reth-chainspec = { workspace = true, default-features = false } +reth-ethereum-forks = { workspace = true, default-features = false } +reth-network-peers = { workspace = true, default-features = false } +reth-primitives-traits = { workspace = true, default-features = false } +reth-trie-common = { workspace = true, default-features = false } + +# scroll +reth-scroll-forks = { workspace = true, default-features = false } +scroll-alloy-hardforks = { workspace = true, default-features = false } + +# ethereum +alloy-chains = { workspace = true, default-features = false } +alloy-genesis = { workspace = true, default-features = false } +alloy-primitives = { workspace = true, default-features = false } +alloy-consensus = { workspace = true, default-features = false } +alloy-eips = { workspace = true, default-features = false } +alloy-serde = { workspace = true, default-features = false } + +# io +serde_json = { workspace = true, default-features = false } +serde = { workspace = true, default-features = false, features = ["derive"] } + +# misc +auto_impl.workspace = true +derive_more = { workspace = true, default-features = false } +once_cell = { workspace = true, default-features = false } + +[dev-dependencies] +alloy-genesis.workspace = true +reth-chainspec = { workspace = true, features = ["test-utils"] } + +[features] +default = ["std"] +std = [ + "alloy-chains/std", + "alloy-genesis/std", + "alloy-primitives/std", + "alloy-eips/std", + "alloy-serde/std", + "reth-chainspec/std", + "reth-ethereum-forks/std", + "reth-primitives-traits/std", + "reth-scroll-forks/std", + "reth-trie-common/std", + "alloy-consensus/std", + "once_cell/std", + "serde/std", + "derive_more/std", + "reth-network-peers/std", + "serde_json/std", + "scroll-alloy-hardforks/std", +]
diff --git reth/crates/scroll/chainspec/res/genesis/dev.json scroll-reth/crates/scroll/chainspec/res/genesis/dev.json new file mode 100644 index 0000000000000000000000000000000000000000..0fca61b85e111a6daded68753fac9e97c27f567f --- /dev/null +++ scroll-reth/crates/scroll/chainspec/res/genesis/dev.json @@ -0,0 +1,87 @@ +{ + "nonce": "0x0", + "timestamp": "0x6490fdd2", + "extraData": "0x", + "gasLimit": "0x1312D00", + "baseFeePerGas": "0x0", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x90F79bf6EB2c4f870365E785982E1f101E93b906": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x976EA74026E726554dB657fA54763abd0C3a0aa9": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xBcd4042DE499D14e55001CcbB24a551F3b954096": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x71bE63f3384f5fb98995898A86B02Fb2426c5788": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xFABB0ac9d68B0B445fB7357272Ff202C5651694a": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xcd3B766CCDd6AE721141F452C550Ca635964ce71": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x2546BcD3c84621e976D8185a91A922aE77ECEc30": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xbDA5747bFD65F08deb54cb465eB87D40e51B197E": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xdD2FD4581271e230360230F9337D5c0430Bf44C0": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x5300000000000000000000000000000000000002": { + "balance": "0xd3c21bcecceda1000000", + "storage": { + "0x01": "0x000000000000000000000000000000000000000000000000000000003758e6b0", + "0x02": "0x0000000000000000000000000000000000000000000000000000000000000038", + "0x03": "0x000000000000000000000000000000000000000000000000000000003e95ba80", + "0x04": "0x0000000000000000000000005300000000000000000000000000000000000003", + "0x05": "0x000000000000000000000000000000000000000000000000000000008390c2c1", + "0x06": "0x00000000000000000000000000000000000000000000000000000069cf265bfe", + "0x07": "0x00000000000000000000000000000000000000000000000000000000168b9aa3" + } + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +}
diff --git reth/crates/scroll/chainspec/res/genesis/scroll.json scroll-reth/crates/scroll/chainspec/res/genesis/scroll.json new file mode 100644 index 0000000000000000000000000000000000000000..ef6360ac9d90367c5029a8f6858c062a88866cef --- /dev/null +++ scroll-reth/crates/scroll/chainspec/res/genesis/scroll.json @@ -0,0 +1 @@ +{"config":{"chainId":534352,"homesteadBlock":0,"eip150Block":0,"eip150Hash":"0x0000000000000000000000000000000000000000000000000000000000000000","eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"berlinBlock":0,"londonBlock":0,"archimedesBlock":0,"shanghaiTime":0,"bernoulliBlock":5220340,"curieBlock":7096836,"darwinTime":1724227200,"darwinV2Time":1725264000,"clique":{"period":3,"epoch":30000,"relaxed_period":true},"scroll":{"useZktrie":true,"maxTxPerBlock":100,"maxTxPayloadBytesPerBlock":122880,"feeVaultAddress":"0x5300000000000000000000000000000000000005","l1Config":{"l1ChainId":"1","l1MessageQueueAddress":"0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B","scrollChainAddress":"0xa13BAF47339d63B743e7Da8741db5456DAc1E556","numL1MessagesPerBlock":"10"}}},"nonce":"0x0","timestamp":"0x6524e860","extraData":"0x4c61206573746f6e7465636f206573746173206d616c6665726d6974612e0000d2ACF5d16a983DB0d909d9D761B8337Fabd6cBd10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","gasLimit":"10000000","difficulty":"0x1","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"0xF9062b8a30e0d7722960e305049FA50b86ba6253":{"balance":"2000000000000000000"},"0x781e90f1c8Fc4611c9b7497C3B47F99Ef6969CbC":{"balance":"226156424291633194186662080095093570025917938800079226637565593765455331328"},"0x5300000000000000000000000000000000000000":{"balance":"0x0","code":"0x608060405234801561001057600080fd5b50600436106100935760003560e01c806383cc76601161006657806383cc7660146100fc5780638da5cb5b1461010f578063c4d66de814610122578063d4b9f4fa14610135578063f2fde38b1461013e57600080fd5b806326aad7b7146100985780633cb747bf146100b4578063600a2e77146100df578063715018a6146100f2575b600080fd5b6100a160015481565b6040519081526020015b60405180910390f35b6053546100c7906001600160a01b031681565b6040516001600160a01b0390911681526020016100ab565b6100a16100ed36600461054a565b610151565b6100fa6101f6565b005b6100a161010a36600461054a565b61022c565b6052546100c7906001600160a01b031681565b6100fa610130366004610563565b610243565b6100a160005481565b6100fa61014c366004610563565b6102db565b6053546000906001600160a01b031633146101a45760405162461bcd60e51b815260206004820152600e60248201526d37b7363c9036b2b9b9b2b733b2b960911b60448201526064015b60405180910390fd5b6000806101b084610367565b60408051838152602081018890529294509092507ffaa617c2d8ce12c62637dbce76efcc18dae60574aa95709bdcedce7e76071693910160405180910390a19392505050565b6052546001600160a01b031633146102205760405162461bcd60e51b815260040161019b90610593565b61022a6000610486565b565b602a816028811061023c57600080fd5b0154905081565b6052546001600160a01b0316331461026d5760405162461bcd60e51b815260040161019b90610593565b600154156102b15760405162461bcd60e51b815260206004820152601160248201527063616e6e6f7420696e697469616c697a6560781b604482015260640161019b565b6102b96104d8565b605380546001600160a01b0319166001600160a01b0392909216919091179055565b6052546001600160a01b031633146103055760405162461bcd60e51b815260040161019b90610593565b6001600160a01b03811661035b5760405162461bcd60e51b815260206004820152601d60248201527f6e6577206f776e657220697320746865207a65726f2061646472657373000000604482015260640161019b565b61036481610486565b50565b60035460009081906103bb5760405162461bcd60e51b815260206004820152601a60248201527f63616c6c206265666f726520696e697469616c697a6174696f6e000000000000604482015260640161019b565b6001548360005b8215610456576103d36002846105e0565b60000361041f5781602a82602881106103ee576103ee6105ca565b01556104188260028360288110610407576104076105ca565b015460009182526020526040902090565b915061044a565b610447602a8260288110610435576104356105ca565b01548360009182526020526040902090565b91505b600192831c92016103c2565b81602a826028811061046a5761046a6105ca565b0155506000819055600180548082019091559590945092505050565b605280546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b60005b60286104e8826001610618565b10156103645761051960028260288110610504576105046105ca565b015460028360288110610407576104076105ca565b6002610526836001610618565b60288110610536576105366105ca565b01558061054281610631565b9150506104db565b60006020828403121561055c57600080fd5b5035919050565b60006020828403121561057557600080fd5b81356001600160a01b038116811461058c57600080fd5b9392505050565b60208082526017908201527f63616c6c6572206973206e6f7420746865206f776e6572000000000000000000604082015260600190565b634e487b7160e01b600052603260045260246000fd5b6000826105fd57634e487b7160e01b600052601260045260246000fd5b500690565b634e487b7160e01b600052601160045260246000fd5b8082018082111561062b5761062b610602565b92915050565b60006001820161064357610643610602565b506001019056fea26469706673582212208fb1cb9933bb17dd0a7c17de7c890919b08d2fd7eb2bede7b41caa32709b30b564736f6c63430008100033","storage":{"0x0000000000000000000000000000000000000000000000000000000000000052":"0xF9062b8a30e0d7722960e305049FA50b86ba6253"}},"0x5300000000000000000000000000000000000002":{"balance":"0x0","code":"0x608060405234801561001057600080fd5b50600436106100cf5760003560e01c8063715018a61161008c578063bede39b511610066578063bede39b51461018d578063de26c4a1146101a0578063f2fde38b146101b3578063f45e65d8146101c657600080fd5b8063715018a6146101475780638da5cb5b1461014f57806393e59dc11461017a57600080fd5b80630c18c162146100d45780633577afc5146100f05780633d0f963e1461010557806349948e0e14610118578063519b4bd31461012b5780637046559714610134575b600080fd5b6100dd60025481565b6040519081526020015b60405180910390f35b6101036100fe366004610671565b6101cf565b005b61010361011336600461068a565b610291565b6100dd6101263660046106d0565b61031c565b6100dd60015481565b610103610142366004610671565b610361565b610103610416565b600054610162906001600160a01b031681565b6040516001600160a01b0390911681526020016100e7565b600454610162906001600160a01b031681565b61010361019b366004610671565b61044c565b6100dd6101ae3660046106d0565b610533565b6101036101c136600461068a565b610595565b6100dd60035481565b6000546001600160a01b031633146102025760405162461bcd60e51b81526004016101f990610781565b60405180910390fd5b621c9c388111156102555760405162461bcd60e51b815260206004820152601760248201527f657863656564206d6178696d756d206f7665726865616400000000000000000060448201526064016101f9565b60028190556040518181527f32740b35c0ea213650f60d44366b4fb211c9033b50714e4a1d34e65d5beb9bb4906020015b60405180910390a150565b6000546001600160a01b031633146102bb5760405162461bcd60e51b81526004016101f990610781565b600480546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f22d1c35fe072d2e42c3c8f9bd4a0d34aa84a0101d020a62517b33fdb3174e5f7910160405180910390a15050565b60008061032883610533565b905060006001548261033a91906107b8565b9050633b9aca006003548261034f91906107b8565b61035991906107e5565b949350505050565b6000546001600160a01b0316331461038b5760405162461bcd60e51b81526004016101f990610781565b61039b633b9aca006103e86107b8565b8111156103e15760405162461bcd60e51b8152602060048201526014602482015273657863656564206d6178696d756d207363616c6560601b60448201526064016101f9565b60038190556040518181527f3336cd9708eaf2769a0f0dc0679f30e80f15dcd88d1921b5a16858e8b85c591a90602001610286565b6000546001600160a01b031633146104405760405162461bcd60e51b81526004016101f990610781565b61044a6000610621565b565b6004805460405163efc7840160e01b815233928101929092526001600160a01b03169063efc7840190602401602060405180830381865afa158015610495573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104b99190610807565b6104fe5760405162461bcd60e51b81526020600482015260166024820152752737ba103bb434ba32b634b9ba32b21039b2b73232b960511b60448201526064016101f9565b60018190556040518181527f351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c4490602001610286565b80516000908190815b818110156105865784818151811061055657610556610829565b01602001516001600160f81b0319166000036105775760048301925061057e565b6010830192505b60010161053c565b50506002540160400192915050565b6000546001600160a01b031633146105bf5760405162461bcd60e51b81526004016101f990610781565b6001600160a01b0381166106155760405162461bcd60e51b815260206004820152601d60248201527f6e6577206f776e657220697320746865207a65726f206164647265737300000060448201526064016101f9565b61061e81610621565b50565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006020828403121561068357600080fd5b5035919050565b60006020828403121561069c57600080fd5b81356001600160a01b03811681146106b357600080fd5b9392505050565b634e487b7160e01b600052604160045260246000fd5b6000602082840312156106e257600080fd5b813567ffffffffffffffff808211156106fa57600080fd5b818401915084601f83011261070e57600080fd5b813581811115610720576107206106ba565b604051601f8201601f19908116603f01168101908382118183101715610748576107486106ba565b8160405282815287602084870101111561076157600080fd5b826020860160208301376000928101602001929092525095945050505050565b60208082526017908201527f63616c6c6572206973206e6f7420746865206f776e6572000000000000000000604082015260600190565b60008160001904831182151516156107e057634e487b7160e01b600052601160045260246000fd5b500290565b60008261080257634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561081957600080fd5b815180151581146106b357600080fd5b634e487b7160e01b600052603260045260246000fdfea26469706673582212205ea335809638809cf032c794fd966e2439020737b1dcc2218435cb438286efcf64736f6c63430008100033","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0xF9062b8a30e0d7722960e305049FA50b86ba6253"}},"0x5300000000000000000000000000000000000003":{"balance":"0x0","code":"0x608060405234801561001057600080fd5b50600436106100575760003560e01c8063715018a61461005c57806379586dd7146100665780638da5cb5b14610079578063efc78401146100a9578063f2fde38b146100e5575b600080fd5b6100646100f8565b005b610064610074366004610356565b610137565b60005461008c906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100d56100b736600461042d565b6001600160a01b031660009081526001602052604090205460ff1690565b60405190151581526020016100a0565b6100646100f336600461042d565b610238565b6000546001600160a01b0316331461012b5760405162461bcd60e51b81526004016101229061044f565b60405180910390fd5b61013560006102c4565b565b6000546001600160a01b031633146101615760405162461bcd60e51b81526004016101229061044f565b60005b825181101561023357816001600085848151811061018457610184610486565b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a81548160ff0219169083151502179055508281815181106101d5576101d5610486565b60200260200101516001600160a01b03167f8daaf060c3306c38e068a75c054bf96ecd85a3db1252712c4d93632744c42e0d83604051610219911515815260200190565b60405180910390a28061022b8161049c565b915050610164565b505050565b6000546001600160a01b031633146102625760405162461bcd60e51b81526004016101229061044f565b6001600160a01b0381166102b85760405162461bcd60e51b815260206004820152601d60248201527f6e6577206f776e657220697320746865207a65726f20616464726573730000006044820152606401610122565b6102c1816102c4565b50565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b634e487b7160e01b600052604160045260246000fd5b80356001600160a01b038116811461034157600080fd5b919050565b8035801515811461034157600080fd5b6000806040838503121561036957600080fd5b823567ffffffffffffffff8082111561038157600080fd5b818501915085601f83011261039557600080fd5b81356020828211156103a9576103a9610314565b8160051b604051601f19603f830116810181811086821117156103ce576103ce610314565b6040529283528183019350848101820192898411156103ec57600080fd5b948201945b83861015610411576104028661032a565b855294820194938201936103f1565b96506104209050878201610346565b9450505050509250929050565b60006020828403121561043f57600080fd5b6104488261032a565b9392505050565b60208082526017908201527f63616c6c6572206973206e6f7420746865206f776e6572000000000000000000604082015260600190565b634e487b7160e01b600052603260045260246000fd5b6000600182016104bc57634e487b7160e01b600052601160045260246000fd5b506001019056fea26469706673582212203414b076e92b618bd7c3437159d7bceb2acc3a5c82f51f383465512d9c52e97064736f6c63430008100033","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0xF9062b8a30e0d7722960e305049FA50b86ba6253"}},"0x5300000000000000000000000000000000000004":{"balance":"0x0","code":"0x6080604052600436106101025760003560e01c806370a0823111610095578063a457c2d711610064578063a457c2d7146102b4578063a9059cbb146102d4578063d0e30db0146102f4578063d505accf146102fc578063dd62ed3e1461031c57600080fd5b806370a08231146102215780637ecebe001461025757806384b0196e1461027757806395d89b411461029f57600080fd5b80632e1a7d4d116100d15780632e1a7d4d146101b0578063313ce567146101d05780633644e515146101ec578063395093511461020157600080fd5b806306fdde0314610116578063095ea7b31461014157806318160ddd1461017157806323b872dd1461019057600080fd5b366101115761010f61033c565b005b600080fd5b34801561012257600080fd5b5061012b61038d565b60405161013891906112fa565b60405180910390f35b34801561014d57600080fd5b5061016161015c366004611330565b61041f565b6040519015158152602001610138565b34801561017d57600080fd5b506002545b604051908152602001610138565b34801561019c57600080fd5b506101616101ab36600461135a565b610439565b3480156101bc57600080fd5b5061010f6101cb366004611396565b61045d565b3480156101dc57600080fd5b5060405160128152602001610138565b3480156101f857600080fd5b5061018261054e565b34801561020d57600080fd5b5061016161021c366004611330565b61055d565b34801561022d57600080fd5b5061018261023c3660046113af565b6001600160a01b031660009081526020819052604090205490565b34801561026357600080fd5b506101826102723660046113af565b61057f565b34801561028357600080fd5b5061028c61059d565b60405161013897969594939291906113ca565b3480156102ab57600080fd5b5061012b610626565b3480156102c057600080fd5b506101616102cf366004611330565b610635565b3480156102e057600080fd5b506101616102ef366004611330565b6106b0565b61010f61033c565b34801561030857600080fd5b5061010f610317366004611460565b6106be565b34801561032857600080fd5b506101826103373660046114d3565b610822565b336103478134610881565b806001600160a01b03167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c3460405161038291815260200190565b60405180910390a250565b60606003805461039c90611506565b80601f01602080910402602001604051908101604052809291908181526020018280546103c890611506565b80156104155780601f106103ea57610100808354040283529160200191610415565b820191906000526020600020905b8154815290600101906020018083116103f857829003601f168201915b5050505050905090565b60003361042d818585610940565b60019150505b92915050565b600033610447858285610a65565b610452858585610adf565b506001949350505050565b336104688183610c83565b6000816001600160a01b03168360405160006040518083038185875af1925050503d80600081146104b5576040519150601f19603f3d011682016040523d82523d6000602084013e6104ba565b606091505b50509050806105065760405162461bcd60e51b81526020600482015260136024820152721dda5d1a191c985dc81155120819985a5b1959606a1b60448201526064015b60405180910390fd5b816001600160a01b03167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b658460405161054191815260200190565b60405180910390a2505050565b6000610558610db2565b905090565b60003361042d8185856105708383610822565b61057a919061153a565b610940565b6001600160a01b038116600090815260076020526040812054610433565b6000606080828080836105d17f577261707065642045746865720000000000000000000000000000000000000d6005610edd565b6105fc7f31000000000000000000000000000000000000000000000000000000000000016006610edd565b60408051600080825260208201909252600f60f81b9b939a50919850469750309650945092509050565b60606004805461039c90611506565b600033816106438286610822565b9050838110156106a35760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084016104fd565b6104528286868403610940565b60003361042d818585610adf565b8342111561070e5760405162461bcd60e51b815260206004820152601d60248201527f45524332305065726d69743a206578706972656420646561646c696e6500000060448201526064016104fd565b60007f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c988888861073d8c610f81565b6040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810186905260e001604051602081830303815290604052805190602001209050600061079882610fa9565b905060006107a882878787610fd6565b9050896001600160a01b0316816001600160a01b03161461080b5760405162461bcd60e51b815260206004820152601e60248201527f45524332305065726d69743a20696e76616c6964207369676e6174757265000060448201526064016104fd565b6108168a8a8a610940565b50505050505050505050565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b60006020835110156108695761086283610ffe565b9050610433565b8161087484826115bf565b5060ff9050610433565b90565b6001600160a01b0382166108d75760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016104fd565b80600260008282546108e9919061153a565b90915550506001600160a01b038216600081815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b6001600160a01b0383166109a25760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016104fd565b6001600160a01b038216610a035760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016104fd565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b6000610a718484610822565b90506000198114610ad95781811015610acc5760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016104fd565b610ad98484848403610940565b50505050565b6001600160a01b038316610b435760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016104fd565b6001600160a01b038216610ba55760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016104fd565b6001600160a01b03831660009081526020819052604090205481811015610c1d5760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b60648201526084016104fd565b6001600160a01b03848116600081815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3610ad9565b6001600160a01b038216610ce35760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b60648201526084016104fd565b6001600160a01b03821660009081526020819052604090205481811015610d575760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b60648201526084016104fd565b6001600160a01b0383166000818152602081815260408083208686039055600280548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9101610a58565b505050565b6000306001600160a01b037f000000000000000000000000530000000000000000000000000000000000000416148015610e0b57507f000000000000000000000000000000000000000000000000000000000008275046145b15610e3557507fe5b117a3cd7ae7ed3508e6e6c5a0794536b2a8dee12533c4d7524eae9c85438f90565b610558604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f00cd3d46df44f2cbb950cf84eb2e92aa2ddd23195b1a009173ea59a063357ed3918101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260009060c00160405160208183030381529060405280519060200120905090565b606060ff8314610ef0576108628361103c565b818054610efc90611506565b80601f0160208091040260200160405190810160405280929190818152602001828054610f2890611506565b8015610f755780601f10610f4a57610100808354040283529160200191610f75565b820191906000526020600020905b815481529060010190602001808311610f5857829003601f168201915b50505050509050610433565b6001600160a01b03811660009081526007602052604090208054600181018255905b50919050565b6000610433610fb6610db2565b8360405161190160f01b8152600281019290925260228201526042902090565b6000806000610fe78787878761107b565b91509150610ff48161113f565b5095945050505050565b600080829050601f81511115611029578260405163305a27a960e01b81526004016104fd91906112fa565b80516110348261167f565b179392505050565b606060006110498361128c565b604080516020808252818301909252919250600091906020820181803683375050509182525060208101929092525090565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08311156110b25750600090506003611136565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa158015611106573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811661112f57600060019250925050611136565b9150600090505b94509492505050565b6000816004811115611153576111536116a3565b0361115b5750565b600181600481111561116f5761116f6116a3565b036111bc5760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e6174757265000000000000000060448201526064016104fd565b60028160048111156111d0576111d06116a3565b0361121d5760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e6774680060448201526064016104fd565b6003816004811115611231576112316116a3565b036112895760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b60648201526084016104fd565b50565b600060ff8216601f81111561043357604051632cd44ac360e21b815260040160405180910390fd5b6000815180845260005b818110156112da576020818501810151868301820152016112be565b506000602082860101526020601f19601f83011685010191505092915050565b60208152600061130d60208301846112b4565b9392505050565b80356001600160a01b038116811461132b57600080fd5b919050565b6000806040838503121561134357600080fd5b61134c83611314565b946020939093013593505050565b60008060006060848603121561136f57600080fd5b61137884611314565b925061138660208501611314565b9150604084013590509250925092565b6000602082840312156113a857600080fd5b5035919050565b6000602082840312156113c157600080fd5b61130d82611314565b60ff60f81b881681526000602060e0818401526113ea60e084018a6112b4565b83810360408501526113fc818a6112b4565b606085018990526001600160a01b038816608086015260a0850187905284810360c0860152855180825283870192509083019060005b8181101561144e57835183529284019291840191600101611432565b50909c9b505050505050505050505050565b600080600080600080600060e0888a03121561147b57600080fd5b61148488611314565b965061149260208901611314565b95506040880135945060608801359350608088013560ff811681146114b657600080fd5b9699959850939692959460a0840135945060c09093013592915050565b600080604083850312156114e657600080fd5b6114ef83611314565b91506114fd60208401611314565b90509250929050565b600181811c9082168061151a57607f821691505b602082108103610fa357634e487b7160e01b600052602260045260246000fd5b8082018082111561043357634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052604160045260246000fd5b601f821115610dad57600081815260208120601f850160051c810160208610156115985750805b601f850160051c820191505b818110156115b7578281556001016115a4565b505050505050565b815167ffffffffffffffff8111156115d9576115d961155b565b6115ed816115e78454611506565b84611571565b602080601f831160018114611622576000841561160a5750858301515b600019600386901b1c1916600185901b1785556115b7565b600085815260208120601f198616915b8281101561165157888601518255948401946001909101908401611632565b508582101561166f5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b80516020808301519190811015610fa35760001960209190910360031b1b16919050565b634e487b7160e01b600052602160045260246000fdfea26469706673582212207f39e33e122e8e2b0381aa6abea46046f56b05ced66c556a06bb1b80be7f55cf64736f6c63430008100033","storage":{"0x0000000000000000000000000000000000000000000000000000000000000003":"0x577261707065642045746865720000000000000000000000000000000000001a","0x0000000000000000000000000000000000000000000000000000000000000004":"0x5745544800000000000000000000000000000000000000000000000000000008"}},"0x5300000000000000000000000000000000000005":{"balance":"0x0","code":"0x6080604052600436106100ab5760003560e01c806384411d651161006457806384411d65146101845780638da5cb5b1461019a5780639e7adc79146101ba578063f2fde38b146101da578063feec756c146101fa578063ff4f35461461021a57600080fd5b80632e1a7d4d146100b75780633cb747bf146100d95780633ccfd60b14610116578063457e1a491461012b57806366d003ac1461014f578063715018a61461016f57600080fd5b366100b257005b600080fd5b3480156100c357600080fd5b506100d76100d2366004610682565b61023a565b005b3480156100e557600080fd5b506002546100f9906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561012257600080fd5b506100d76103ff565b34801561013757600080fd5b5061014160015481565b60405190815260200161010d565b34801561015b57600080fd5b506003546100f9906001600160a01b031681565b34801561017b57600080fd5b506100d761040c565b34801561019057600080fd5b5061014160045481565b3480156101a657600080fd5b506000546100f9906001600160a01b031681565b3480156101c657600080fd5b506100d76101d536600461069b565b610442565b3480156101e657600080fd5b506100d76101f536600461069b565b6104be565b34801561020657600080fd5b506100d761021536600461069b565b610547565b34801561022657600080fd5b506100d7610235366004610682565b6105c3565b6001548110156102ca5760405162461bcd60e51b815260206004820152604a60248201527f4665655661756c743a207769746864726177616c20616d6f756e74206d75737460448201527f2062652067726561746572207468616e206d696e696d756d20776974686472616064820152691dd85b08185b5bdd5b9d60b21b608482015260a4015b60405180910390fd5b478082111561032e5760405162461bcd60e51b815260206004820152602a60248201527f4665655661756c743a20696e73756666696369656e742062616c616e636520746044820152696f20776974686472617760b01b60648201526084016102c1565b6004805483019055600354604080518481526001600160a01b0390921660208301523382820152517fc8a211cc64b6ed1b50595a9fcb1932b6d1e5a6e8ef15b60e5b1f988ea9086bba9181900360600190a1600254600354604080516020810182526000808252915163b2267a7b60e01b81526001600160a01b039485169463b2267a7b9488946103c99491909216928592906004016106cb565b6000604051808303818588803b1580156103e257600080fd5b505af11580156103f6573d6000803e3d6000fd5b50505050505050565b476104098161023a565b50565b6000546001600160a01b031633146104365760405162461bcd60e51b81526004016102c190610737565b6104406000610632565b565b6000546001600160a01b0316331461046c5760405162461bcd60e51b81526004016102c190610737565b600280546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f1c928c417a10a21c3cddad148c5dba5d710e4b1442d6d8a36de345935ad8461290600090a35050565b6000546001600160a01b031633146104e85760405162461bcd60e51b81526004016102c190610737565b6001600160a01b03811661053e5760405162461bcd60e51b815260206004820152601d60248201527f6e6577206f776e657220697320746865207a65726f206164647265737300000060448201526064016102c1565b61040981610632565b6000546001600160a01b031633146105715760405162461bcd60e51b81526004016102c190610737565b600380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f7e1e96961a397c8aa26162fe259cc837afc95e33aad4945ddc61c18dabb7a6ad90600090a35050565b6000546001600160a01b031633146105ed5760405162461bcd60e51b81526004016102c190610737565b600180549082905560408051828152602081018490527f0d3c80219fe57713b9f9c83d1e51426792d0c14d8e330e65b102571816140965910160405180910390a15050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006020828403121561069457600080fd5b5035919050565b6000602082840312156106ad57600080fd5b81356001600160a01b03811681146106c457600080fd5b9392505050565b60018060a01b038516815260006020858184015260806040840152845180608085015260005b8181101561070d5786810183015185820160a0015282016106f1565b50600060a0828601015260a0601f19601f8301168501019250505082606083015295945050505050565b60208082526017908201527f63616c6c6572206973206e6f7420746865206f776e657200000000000000000060408201526060019056fea2646970667358221220063c6c384f745ebcacfdd13320e5b9a50687aae43ff14566761f56273111b97e64736f6c63430008100033","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0xF9062b8a30e0d7722960e305049FA50b86ba6253","0x0000000000000000000000000000000000000000000000000000000000000001":"0x8ac7230489e80000","0x0000000000000000000000000000000000000000000000000000000000000002":"0x781e90f1c8Fc4611c9b7497C3B47F99Ef6969CbC","0x0000000000000000000000000000000000000000000000000000000000000003":"0x8FA3b4570B4C96f8036C13b64971BA65867eEB48"}}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","baseFeePerGas":null}
diff --git reth/crates/scroll/chainspec/res/genesis/sepolia_scroll.json scroll-reth/crates/scroll/chainspec/res/genesis/sepolia_scroll.json new file mode 100644 index 0000000000000000000000000000000000000000..64987e5c8c5eed66d6f1927b5f8f0b6d31b18de9 --- /dev/null +++ scroll-reth/crates/scroll/chainspec/res/genesis/sepolia_scroll.json @@ -0,0 +1 @@ +{"config":{"chainId":534351,"homesteadBlock":0,"eip150Block":0,"eip150Hash":"0x0000000000000000000000000000000000000000000000000000000000000000","eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"berlinBlock":0,"londonBlock":0,"archimedesBlock":0,"shanghaiBlock":0,"bernoulliBlock":3747132,"curieBlock":4740239,"clique":{"period":3,"epoch":30000,"relaxed_period":true},"scroll":{"useZktrie":true,"maxTxPerBlock":100,"maxTxPayloadBytesPerBlock":122880,"feeVaultAddress":"0x5300000000000000000000000000000000000005","enableEIP2718":false,"enableEIP1559":false,"l1Config":{"l1ChainId":"11155111","l1MessageQueueAddress":"0xF0B2293F5D834eAe920c6974D50957A1732de763","scrollChainAddress":"0x2D567EcE699Eabe5afCd141eDB7A4f2D0D6ce8a0","numL1MessagesPerBlock":"10"}}},"nonce":"0x0","timestamp":"0x64cfd015","extraData":"0x000000000000000000000000000000000000000000000000000000000000000048C3F81f3D998b6652900e1C3183736C238Fe4290000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","gasLimit":"8000000","difficulty":"0x1","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"0x18960EEc21b1878C581937a14c5c3C43008F6b6B":{"balance":"10000000000000000000"},"0xBa50f5340FB9F3Bd074bD638c9BE13eCB36E603d":{"balance":"226156424291633194186662080095093570025917938800079226629565593765455331328"},"0x5300000000000000000000000000000000000000":{"balance":"0x0","code":"0x608060405234801561001057600080fd5b50600436106100935760003560e01c806383cc76601161006657806383cc7660146100fc5780638da5cb5b1461010f578063c4d66de814610122578063d4b9f4fa14610135578063f2fde38b1461013e57600080fd5b806326aad7b7146100985780633cb747bf146100b4578063600a2e77146100df578063715018a6146100f2575b600080fd5b6100a160015481565b6040519081526020015b60405180910390f35b6053546100c7906001600160a01b031681565b6040516001600160a01b0390911681526020016100ab565b6100a16100ed36600461054a565b610151565b6100fa6101f6565b005b6100a161010a36600461054a565b61022c565b6052546100c7906001600160a01b031681565b6100fa610130366004610563565b610243565b6100a160005481565b6100fa61014c366004610563565b6102db565b6053546000906001600160a01b031633146101a45760405162461bcd60e51b815260206004820152600e60248201526d37b7363c9036b2b9b9b2b733b2b960911b60448201526064015b60405180910390fd5b6000806101b084610367565b60408051838152602081018890529294509092507ffaa617c2d8ce12c62637dbce76efcc18dae60574aa95709bdcedce7e76071693910160405180910390a19392505050565b6052546001600160a01b031633146102205760405162461bcd60e51b815260040161019b90610593565b61022a6000610486565b565b602a816028811061023c57600080fd5b0154905081565b6052546001600160a01b0316331461026d5760405162461bcd60e51b815260040161019b90610593565b600154156102b15760405162461bcd60e51b815260206004820152601160248201527063616e6e6f7420696e697469616c697a6560781b604482015260640161019b565b6102b96104d8565b605380546001600160a01b0319166001600160a01b0392909216919091179055565b6052546001600160a01b031633146103055760405162461bcd60e51b815260040161019b90610593565b6001600160a01b03811661035b5760405162461bcd60e51b815260206004820152601d60248201527f6e6577206f776e657220697320746865207a65726f2061646472657373000000604482015260640161019b565b61036481610486565b50565b60035460009081906103bb5760405162461bcd60e51b815260206004820152601a60248201527f63616c6c206265666f726520696e697469616c697a6174696f6e000000000000604482015260640161019b565b6001548360005b8215610456576103d36002846105e0565b60000361041f5781602a82602881106103ee576103ee6105ca565b01556104188260028360288110610407576104076105ca565b015460009182526020526040902090565b915061044a565b610447602a8260288110610435576104356105ca565b01548360009182526020526040902090565b91505b600192831c92016103c2565b81602a826028811061046a5761046a6105ca565b0155506000819055600180548082019091559590945092505050565b605280546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b60005b60286104e8826001610618565b10156103645761051960028260288110610504576105046105ca565b015460028360288110610407576104076105ca565b6002610526836001610618565b60288110610536576105366105ca565b01558061054281610631565b9150506104db565b60006020828403121561055c57600080fd5b5035919050565b60006020828403121561057557600080fd5b81356001600160a01b038116811461058c57600080fd5b9392505050565b60208082526017908201527f63616c6c6572206973206e6f7420746865206f776e6572000000000000000000604082015260600190565b634e487b7160e01b600052603260045260246000fd5b6000826105fd57634e487b7160e01b600052601260045260246000fd5b500690565b634e487b7160e01b600052601160045260246000fd5b8082018082111561062b5761062b610602565b92915050565b60006001820161064357610643610602565b506001019056fea26469706673582212208fb1cb9933bb17dd0a7c17de7c890919b08d2fd7eb2bede7b41caa32709b30b564736f6c63430008100033","storage":{"0x0000000000000000000000000000000000000000000000000000000000000052":"0x18960EEc21b1878C581937a14c5c3C43008F6b6B"}},"0x5300000000000000000000000000000000000002":{"balance":"0x0","code":"0x608060405234801561001057600080fd5b50600436106100cf5760003560e01c8063715018a61161008c578063bede39b511610066578063bede39b51461018d578063de26c4a1146101a0578063f2fde38b146101b3578063f45e65d8146101c657600080fd5b8063715018a6146101475780638da5cb5b1461014f57806393e59dc11461017a57600080fd5b80630c18c162146100d45780633577afc5146100f05780633d0f963e1461010557806349948e0e14610118578063519b4bd31461012b5780637046559714610134575b600080fd5b6100dd60025481565b6040519081526020015b60405180910390f35b6101036100fe366004610671565b6101cf565b005b61010361011336600461068a565b610291565b6100dd6101263660046106d0565b61031c565b6100dd60015481565b610103610142366004610671565b610361565b610103610416565b600054610162906001600160a01b031681565b6040516001600160a01b0390911681526020016100e7565b600454610162906001600160a01b031681565b61010361019b366004610671565b61044c565b6100dd6101ae3660046106d0565b610533565b6101036101c136600461068a565b610595565b6100dd60035481565b6000546001600160a01b031633146102025760405162461bcd60e51b81526004016101f990610781565b60405180910390fd5b621c9c388111156102555760405162461bcd60e51b815260206004820152601760248201527f657863656564206d6178696d756d206f7665726865616400000000000000000060448201526064016101f9565b60028190556040518181527f32740b35c0ea213650f60d44366b4fb211c9033b50714e4a1d34e65d5beb9bb4906020015b60405180910390a150565b6000546001600160a01b031633146102bb5760405162461bcd60e51b81526004016101f990610781565b600480546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f22d1c35fe072d2e42c3c8f9bd4a0d34aa84a0101d020a62517b33fdb3174e5f7910160405180910390a15050565b60008061032883610533565b905060006001548261033a91906107b8565b9050633b9aca006003548261034f91906107b8565b61035991906107e5565b949350505050565b6000546001600160a01b0316331461038b5760405162461bcd60e51b81526004016101f990610781565b61039b633b9aca006103e86107b8565b8111156103e15760405162461bcd60e51b8152602060048201526014602482015273657863656564206d6178696d756d207363616c6560601b60448201526064016101f9565b60038190556040518181527f3336cd9708eaf2769a0f0dc0679f30e80f15dcd88d1921b5a16858e8b85c591a90602001610286565b6000546001600160a01b031633146104405760405162461bcd60e51b81526004016101f990610781565b61044a6000610621565b565b6004805460405163efc7840160e01b815233928101929092526001600160a01b03169063efc7840190602401602060405180830381865afa158015610495573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104b99190610807565b6104fe5760405162461bcd60e51b81526020600482015260166024820152752737ba103bb434ba32b634b9ba32b21039b2b73232b960511b60448201526064016101f9565b60018190556040518181527f351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c4490602001610286565b80516000908190815b818110156105865784818151811061055657610556610829565b01602001516001600160f81b0319166000036105775760048301925061057e565b6010830192505b60010161053c565b50506002540160400192915050565b6000546001600160a01b031633146105bf5760405162461bcd60e51b81526004016101f990610781565b6001600160a01b0381166106155760405162461bcd60e51b815260206004820152601d60248201527f6e6577206f776e657220697320746865207a65726f206164647265737300000060448201526064016101f9565b61061e81610621565b50565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006020828403121561068357600080fd5b5035919050565b60006020828403121561069c57600080fd5b81356001600160a01b03811681146106b357600080fd5b9392505050565b634e487b7160e01b600052604160045260246000fd5b6000602082840312156106e257600080fd5b813567ffffffffffffffff808211156106fa57600080fd5b818401915084601f83011261070e57600080fd5b813581811115610720576107206106ba565b604051601f8201601f19908116603f01168101908382118183101715610748576107486106ba565b8160405282815287602084870101111561076157600080fd5b826020860160208301376000928101602001929092525095945050505050565b60208082526017908201527f63616c6c6572206973206e6f7420746865206f776e6572000000000000000000604082015260600190565b60008160001904831182151516156107e057634e487b7160e01b600052601160045260246000fd5b500290565b60008261080257634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561081957600080fd5b815180151581146106b357600080fd5b634e487b7160e01b600052603260045260246000fdfea26469706673582212205ea335809638809cf032c794fd966e2439020737b1dcc2218435cb438286efcf64736f6c63430008100033","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x18960EEc21b1878C581937a14c5c3C43008F6b6B"}},"0x5300000000000000000000000000000000000003":{"balance":"0x0","code":"0x608060405234801561001057600080fd5b50600436106100575760003560e01c8063715018a61461005c57806379586dd7146100665780638da5cb5b14610079578063efc78401146100a9578063f2fde38b146100e5575b600080fd5b6100646100f8565b005b610064610074366004610356565b610137565b60005461008c906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6100d56100b736600461042d565b6001600160a01b031660009081526001602052604090205460ff1690565b60405190151581526020016100a0565b6100646100f336600461042d565b610238565b6000546001600160a01b0316331461012b5760405162461bcd60e51b81526004016101229061044f565b60405180910390fd5b61013560006102c4565b565b6000546001600160a01b031633146101615760405162461bcd60e51b81526004016101229061044f565b60005b825181101561023357816001600085848151811061018457610184610486565b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a81548160ff0219169083151502179055508281815181106101d5576101d5610486565b60200260200101516001600160a01b03167f8daaf060c3306c38e068a75c054bf96ecd85a3db1252712c4d93632744c42e0d83604051610219911515815260200190565b60405180910390a28061022b8161049c565b915050610164565b505050565b6000546001600160a01b031633146102625760405162461bcd60e51b81526004016101229061044f565b6001600160a01b0381166102b85760405162461bcd60e51b815260206004820152601d60248201527f6e6577206f776e657220697320746865207a65726f20616464726573730000006044820152606401610122565b6102c1816102c4565b50565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b634e487b7160e01b600052604160045260246000fd5b80356001600160a01b038116811461034157600080fd5b919050565b8035801515811461034157600080fd5b6000806040838503121561036957600080fd5b823567ffffffffffffffff8082111561038157600080fd5b818501915085601f83011261039557600080fd5b81356020828211156103a9576103a9610314565b8160051b604051601f19603f830116810181811086821117156103ce576103ce610314565b6040529283528183019350848101820192898411156103ec57600080fd5b948201945b83861015610411576104028661032a565b855294820194938201936103f1565b96506104209050878201610346565b9450505050509250929050565b60006020828403121561043f57600080fd5b6104488261032a565b9392505050565b60208082526017908201527f63616c6c6572206973206e6f7420746865206f776e6572000000000000000000604082015260600190565b634e487b7160e01b600052603260045260246000fd5b6000600182016104bc57634e487b7160e01b600052601160045260246000fd5b506001019056fea26469706673582212203414b076e92b618bd7c3437159d7bceb2acc3a5c82f51f383465512d9c52e97064736f6c63430008100033","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x18960EEc21b1878C581937a14c5c3C43008F6b6B"}},"0x5300000000000000000000000000000000000004":{"balance":"0x0","code":"0x6080604052600436106101025760003560e01c806370a0823111610095578063a457c2d711610064578063a457c2d7146102b4578063a9059cbb146102d4578063d0e30db0146102f4578063d505accf146102fc578063dd62ed3e1461031c57600080fd5b806370a08231146102215780637ecebe001461025757806384b0196e1461027757806395d89b411461029f57600080fd5b80632e1a7d4d116100d15780632e1a7d4d146101b0578063313ce567146101d05780633644e515146101ec578063395093511461020157600080fd5b806306fdde0314610116578063095ea7b31461014157806318160ddd1461017157806323b872dd1461019057600080fd5b366101115761010f61033c565b005b600080fd5b34801561012257600080fd5b5061012b61037d565b60405161013891906112cf565b60405180910390f35b34801561014d57600080fd5b5061016161015c366004611305565b61040f565b6040519015158152602001610138565b34801561017d57600080fd5b506002545b604051908152602001610138565b34801561019c57600080fd5b506101616101ab36600461132f565b610429565b3480156101bc57600080fd5b5061010f6101cb36600461136b565b61044d565b3480156101dc57600080fd5b5060405160128152602001610138565b3480156101f857600080fd5b50610182610523565b34801561020d57600080fd5b5061016161021c366004611305565b610532565b34801561022d57600080fd5b5061018261023c366004611384565b6001600160a01b031660009081526020819052604090205490565b34801561026357600080fd5b50610182610272366004611384565b610554565b34801561028357600080fd5b5061028c610572565b604051610138979695949392919061139f565b3480156102ab57600080fd5b5061012b6105fb565b3480156102c057600080fd5b506101616102cf366004611305565b61060a565b3480156102e057600080fd5b506101616102ef366004611305565b610685565b61010f61033c565b34801561030857600080fd5b5061010f610317366004611435565b610693565b34801561032857600080fd5b506101826103373660046114a8565b6107f7565b6103463334610856565b60405134815233907fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c9060200160405180910390a2565b60606003805461038c906114db565b80601f01602080910402602001604051908101604052809291908181526020018280546103b8906114db565b80156104055780601f106103da57610100808354040283529160200191610405565b820191906000526020600020905b8154815290600101906020018083116103e857829003601f168201915b5050505050905090565b60003361041d818585610915565b60019150505b92915050565b600033610437858285610a3a565b610442858585610ab4565b506001949350505050565b6104573382610c58565b604051600090339083908381818185875af1925050503d8060008114610499576040519150601f19603f3d011682016040523d82523d6000602084013e61049e565b606091505b50509050806104ea5760405162461bcd60e51b81526020600482015260136024820152721dda5d1a191c985dc81155120819985a5b1959606a1b60448201526064015b60405180910390fd5b60405182815233907f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b659060200160405180910390a25050565b600061052d610d87565b905090565b60003361041d81858561054583836107f7565b61054f919061150f565b610915565b6001600160a01b038116600090815260076020526040812054610423565b6000606080828080836105a67f577261707065642045746865720000000000000000000000000000000000000d6005610eb2565b6105d17f31000000000000000000000000000000000000000000000000000000000000016006610eb2565b60408051600080825260208201909252600f60f81b9b939a50919850469750309650945092509050565b60606004805461038c906114db565b6000338161061882866107f7565b9050838110156106785760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084016104e1565b6104428286868403610915565b60003361041d818585610ab4565b834211156106e35760405162461bcd60e51b815260206004820152601d60248201527f45524332305065726d69743a206578706972656420646561646c696e6500000060448201526064016104e1565b60007f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98888886107128c610f56565b6040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810186905260e001604051602081830303815290604052805190602001209050600061076d82610f7e565b9050600061077d82878787610fab565b9050896001600160a01b0316816001600160a01b0316146107e05760405162461bcd60e51b815260206004820152601e60248201527f45524332305065726d69743a20696e76616c6964207369676e6174757265000060448201526064016104e1565b6107eb8a8a8a610915565b50505050505050505050565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b600060208351101561083e5761083783610fd3565b9050610423565b816108498482611594565b5060ff9050610423565b90565b6001600160a01b0382166108ac5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016104e1565b80600260008282546108be919061150f565b90915550506001600160a01b038216600081815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b6001600160a01b0383166109775760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016104e1565b6001600160a01b0382166109d85760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016104e1565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b6000610a4684846107f7565b90506000198114610aae5781811015610aa15760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016104e1565b610aae8484848403610915565b50505050565b6001600160a01b038316610b185760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016104e1565b6001600160a01b038216610b7a5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016104e1565b6001600160a01b03831660009081526020819052604090205481811015610bf25760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b60648201526084016104e1565b6001600160a01b03848116600081815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3610aae565b6001600160a01b038216610cb85760405162461bcd60e51b815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f206164647265736044820152607360f81b60648201526084016104e1565b6001600160a01b03821660009081526020819052604090205481811015610d2c5760405162461bcd60e51b815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e604482015261636560f01b60648201526084016104e1565b6001600160a01b0383166000818152602081815260408083208686039055600280548790039055518581529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9101610a2d565b505050565b6000306001600160a01b037f0000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa316148015610de057507f000000000000000000000000000000000000000000000000000000000008274f46145b15610e0a57507f624453decb4e78ca99c7630ff9f52222ea6f559f0a6c1bb60b935ef006fa159e90565b61052d604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f00cd3d46df44f2cbb950cf84eb2e92aa2ddd23195b1a009173ea59a063357ed3918101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260009060c00160405160208183030381529060405280519060200120905090565b606060ff8314610ec55761083783611011565b818054610ed1906114db565b80601f0160208091040260200160405190810160405280929190818152602001828054610efd906114db565b8015610f4a5780601f10610f1f57610100808354040283529160200191610f4a565b820191906000526020600020905b815481529060010190602001808311610f2d57829003601f168201915b50505050509050610423565b6001600160a01b03811660009081526007602052604090208054600181018255905b50919050565b6000610423610f8b610d87565b8360405161190160f01b8152600281019290925260228201526042902090565b6000806000610fbc87878787611050565b91509150610fc981611114565b5095945050505050565b600080829050601f81511115610ffe578260405163305a27a960e01b81526004016104e191906112cf565b805161100982611654565b179392505050565b6060600061101e83611261565b604080516020808252818301909252919250600091906020820181803683375050509182525060208101929092525090565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0831115611087575060009050600361110b565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa1580156110db573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381166111045760006001925092505061110b565b9150600090505b94509492505050565b600081600481111561112857611128611678565b036111305750565b600181600481111561114457611144611678565b036111915760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e6174757265000000000000000060448201526064016104e1565b60028160048111156111a5576111a5611678565b036111f25760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e6774680060448201526064016104e1565b600381600481111561120657611206611678565b0361125e5760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b60648201526084016104e1565b50565b600060ff8216601f81111561042357604051632cd44ac360e21b815260040160405180910390fd5b6000815180845260005b818110156112af57602081850181015186830182015201611293565b506000602082860101526020601f19601f83011685010191505092915050565b6020815260006112e26020830184611289565b9392505050565b80356001600160a01b038116811461130057600080fd5b919050565b6000806040838503121561131857600080fd5b611321836112e9565b946020939093013593505050565b60008060006060848603121561134457600080fd5b61134d846112e9565b925061135b602085016112e9565b9150604084013590509250925092565b60006020828403121561137d57600080fd5b5035919050565b60006020828403121561139657600080fd5b6112e2826112e9565b60ff60f81b881681526000602060e0818401526113bf60e084018a611289565b83810360408501526113d1818a611289565b606085018990526001600160a01b038816608086015260a0850187905284810360c0860152855180825283870192509083019060005b8181101561142357835183529284019291840191600101611407565b50909c9b505050505050505050505050565b600080600080600080600060e0888a03121561145057600080fd5b611459886112e9565b9650611467602089016112e9565b95506040880135945060608801359350608088013560ff8116811461148b57600080fd5b9699959850939692959460a0840135945060c09093013592915050565b600080604083850312156114bb57600080fd5b6114c4836112e9565b91506114d2602084016112e9565b90509250929050565b600181811c908216806114ef57607f821691505b602082108103610f7857634e487b7160e01b600052602260045260246000fd5b8082018082111561042357634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052604160045260246000fd5b601f821115610d8257600081815260208120601f850160051c8101602086101561156d5750805b601f850160051c820191505b8181101561158c57828155600101611579565b505050505050565b815167ffffffffffffffff8111156115ae576115ae611530565b6115c2816115bc84546114db565b84611546565b602080601f8311600181146115f757600084156115df5750858301515b600019600386901b1c1916600185901b17855561158c565b600085815260208120601f198616915b8281101561162657888601518255948401946001909101908401611607565b50858210156116445787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b80516020808301519190811015610f785760001960209190910360031b1b16919050565b634e487b7160e01b600052602160045260246000fdfea264697066735822122075458b204a41338df799effa8b73c6c1a17e612bc3b3311c0cec123c4da7709964736f6c63430008100033","storage":{"0x0000000000000000000000000000000000000000000000000000000000000003":"0x577261707065642045746865720000000000000000000000000000000000001a","0x0000000000000000000000000000000000000000000000000000000000000004":"0x5745544800000000000000000000000000000000000000000000000000000008"}},"0x5300000000000000000000000000000000000005":{"balance":"0x0","code":"0x6080604052600436106100a05760003560e01c806384411d651161006457806384411d65146101595780638da5cb5b1461016f5780639e7adc791461018f578063f2fde38b146101af578063feec756c146101cf578063ff4f3546146101ef57600080fd5b80633cb747bf146100ac5780633ccfd60b146100e9578063457e1a491461010057806366d003ac14610124578063715018a61461014457600080fd5b366100a757005b600080fd5b3480156100b857600080fd5b506002546100cc906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156100f557600080fd5b506100fe61020f565b005b34801561010c57600080fd5b5061011660015481565b6040519081526020016100e0565b34801561013057600080fd5b506003546100cc906001600160a01b031681565b34801561015057600080fd5b506100fe610371565b34801561016557600080fd5b5061011660045481565b34801561017b57600080fd5b506000546100cc906001600160a01b031681565b34801561019b57600080fd5b506100fe6101aa3660046105ea565b6103a7565b3480156101bb57600080fd5b506100fe6101ca3660046105ea565b610423565b3480156101db57600080fd5b506100fe6101ea3660046105ea565b6104af565b3480156101fb57600080fd5b506100fe61020a36600461061a565b61052b565b60015447908110156102a15760405162461bcd60e51b815260206004820152604a60248201527f4665655661756c743a207769746864726177616c20616d6f756e74206d75737460448201527f2062652067726561746572207468616e206d696e696d756d20776974686472616064820152691dd85b08185b5bdd5b9d60b21b608482015260a4015b60405180910390fd5b6004805482019055600354604080518381526001600160a01b0390921660208301523382820152517fc8a211cc64b6ed1b50595a9fcb1932b6d1e5a6e8ef15b60e5b1f988ea9086bba9181900360600190a1600254600354604080516020810182526000808252915163b2267a7b60e01b81526001600160a01b039485169463b2267a7b94879461033c949190921692859290600401610633565b6000604051808303818588803b15801561035557600080fd5b505af1158015610369573d6000803e3d6000fd5b505050505050565b6000546001600160a01b0316331461039b5760405162461bcd60e51b81526004016102989061069f565b6103a5600061059a565b565b6000546001600160a01b031633146103d15760405162461bcd60e51b81526004016102989061069f565b600280546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f1c928c417a10a21c3cddad148c5dba5d710e4b1442d6d8a36de345935ad8461290600090a35050565b6000546001600160a01b0316331461044d5760405162461bcd60e51b81526004016102989061069f565b6001600160a01b0381166104a35760405162461bcd60e51b815260206004820152601d60248201527f6e6577206f776e657220697320746865207a65726f20616464726573730000006044820152606401610298565b6104ac8161059a565b50565b6000546001600160a01b031633146104d95760405162461bcd60e51b81526004016102989061069f565b600380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f7e1e96961a397c8aa26162fe259cc837afc95e33aad4945ddc61c18dabb7a6ad90600090a35050565b6000546001600160a01b031633146105555760405162461bcd60e51b81526004016102989061069f565b600180549082905560408051828152602081018490527f0d3c80219fe57713b9f9c83d1e51426792d0c14d8e330e65b102571816140965910160405180910390a15050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6000602082840312156105fc57600080fd5b81356001600160a01b038116811461061357600080fd5b9392505050565b60006020828403121561062c57600080fd5b5035919050565b60018060a01b038516815260006020858184015260806040840152845180608085015260005b818110156106755786810183015185820160a001528201610659565b50600060a0828601015260a0601f19601f8301168501019250505082606083015295945050505050565b60208082526017908201527f63616c6c6572206973206e6f7420746865206f776e657200000000000000000060408201526060019056fea26469706673582212200c5bec0af207d4c7845829d5330f295a5f16702ab8bde670ae90be68974af0a764736f6c63430008100033","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x18960EEc21b1878C581937a14c5c3C43008F6b6B","0x0000000000000000000000000000000000000000000000000000000000000001":"0x8ac7230489e80000","0x0000000000000000000000000000000000000000000000000000000000000002":"0xBa50f5340FB9F3Bd074bD638c9BE13eCB36E603d","0x0000000000000000000000000000000000000000000000000000000000000003":"0x2351C7aD0c8cFEB25c81301EAC922ab1f1980bbe"}}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","baseFeePerGas":null}
diff --git reth/crates/scroll/chainspec/src/constants.rs scroll-reth/crates/scroll/chainspec/src/constants.rs new file mode 100644 index 0000000000000000000000000000000000000000..a95fba0ee468f5e89356087dfe3bbc64292f7bd9 --- /dev/null +++ scroll-reth/crates/scroll/chainspec/src/constants.rs @@ -0,0 +1,122 @@ +use crate::genesis::L1Config; +use alloy_eips::eip1559::BaseFeeParams; +use alloy_primitives::{address, b256, Address, B256}; + +/// The transaction fee recipient on the L2. +pub const SCROLL_FEE_VAULT_ADDRESS: Address = address!("5300000000000000000000000000000000000005"); + +/// The maximum size in bytes of the payload for a block. +pub const MAX_TX_PAYLOAD_BYTES_PER_BLOCK: usize = 120 * 1024; + +/// The system contract on L2 mainnet. +pub const SCROLL_MAINNET_L2_SYSTEM_CONFIG_CONTRACT_ADDRESS: Address = + address!("331A873a2a85219863d80d248F9e2978fE88D0Ea"); + +/// The L1 message queue address for Scroll mainnet. +/// <https://etherscan.io/address/0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B>. +pub const SCROLL_MAINNET_L1_MESSAGE_QUEUE_ADDRESS: Address = + address!("0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B"); + +/// The L1 message queue v2 address for Scroll mainnet. +/// <https://etherscan.io/address/0x56971da63A3C0205184FEF096E9ddFc7A8C2D18a>. +pub const SCROLL_MAINNET_L1_MESSAGE_QUEUE_V2_ADDRESS: Address = + address!("56971da63A3C0205184FEF096E9ddFc7A8C2D18a"); + +/// The L1 proxy address for Scroll mainnet. +/// <https://etherscan.io/address/0xa13BAF47339d63B743e7Da8741db5456DAc1E556>. +pub const SCROLL_MAINNET_L1_PROXY_ADDRESS: Address = + address!("a13BAF47339d63B743e7Da8741db5456DAc1E556"); + +/// The maximum allowed l1 messages per block for Scroll mainnet. +pub const SCROLL_MAINNET_MAX_L1_MESSAGES: u64 = 10; + +/// The L1 configuration for Scroll mainnet. +pub const SCROLL_MAINNET_L1_CONFIG: L1Config = L1Config { + l1_chain_id: alloy_chains::NamedChain::Mainnet as u64, + l1_message_queue_address: SCROLL_MAINNET_L1_MESSAGE_QUEUE_ADDRESS, + l1_message_queue_v2_address: SCROLL_MAINNET_L1_MESSAGE_QUEUE_V2_ADDRESS, + l2_system_config_address: SCROLL_MAINNET_L2_SYSTEM_CONFIG_CONTRACT_ADDRESS, + scroll_chain_address: SCROLL_MAINNET_L1_PROXY_ADDRESS, + num_l1_messages_per_block: SCROLL_MAINNET_MAX_L1_MESSAGES, +}; + +/// The Scroll Mainnet genesis hash +pub const SCROLL_MAINNET_GENESIS_HASH: B256 = + b256!("bbc05efd412b7cd47a2ed0e5ddfcf87af251e414ea4c801d78b6784513180a80"); + +/// The system contract on L2 sepolia. +pub const SCROLL_SEPOLIA_L2_SYSTEM_CONFIG_CONTRACT_ADDRESS: Address = + address!("F444cF06A3E3724e20B35c2989d3942ea8b59124"); + +/// The L1 message queue address for Scroll sepolia. +/// <https://sepolia.etherscan.io/address/0xF0B2293F5D834eAe920c6974D50957A1732de763>. +pub const SCROLL_SEPOLIA_L1_MESSAGE_QUEUE_ADDRESS: Address = + address!("F0B2293F5D834eAe920c6974D50957A1732de763"); + +/// The L1 message queue address v2 for Scroll sepolia. +/// <https://sepolia.etherscan.io/address/0xA0673eC0A48aa924f067F1274EcD281A10c5f19F>. +pub const SCROLL_SEPOLIA_L1_MESSAGE_QUEUE_V2_ADDRESS: Address = + address!("A0673eC0A48aa924f067F1274EcD281A10c5f19F"); + +/// The L1 proxy address for Scroll sepolia. +/// <https://sepolia.etherscan.io/address/0x2D567EcE699Eabe5afCd141eDB7A4f2D0D6ce8a0> +pub const SCROLL_SEPOLIA_L1_PROXY_ADDRESS: Address = + address!("2D567EcE699Eabe5afCd141eDB7A4f2D0D6ce8a0"); + +/// The maximum allowed l1 messages per block for Scroll sepolia. +pub const SCROLL_SEPOLIA_MAX_L1_MESSAGES: u64 = 10; + +/// The L1 configuration for Scroll sepolia. +pub const SCROLL_SEPOLIA_L1_CONFIG: L1Config = L1Config { + l1_chain_id: alloy_chains::NamedChain::Sepolia as u64, + l1_message_queue_address: SCROLL_SEPOLIA_L1_MESSAGE_QUEUE_ADDRESS, + l1_message_queue_v2_address: SCROLL_SEPOLIA_L1_MESSAGE_QUEUE_V2_ADDRESS, + l2_system_config_address: SCROLL_SEPOLIA_L2_SYSTEM_CONFIG_CONTRACT_ADDRESS, + scroll_chain_address: SCROLL_SEPOLIA_L1_PROXY_ADDRESS, + num_l1_messages_per_block: SCROLL_SEPOLIA_MAX_L1_MESSAGES, +}; + +/// The system contract on devnet. +pub const SCROLL_DEV_L2_SYSTEM_CONFIG_CONTRACT_ADDRESS: Address = + address!("0000000000000000000000000000000000000000"); + +/// The L1 message queue address for Scroll dev. +pub const SCROLL_DEV_L1_MESSAGE_QUEUE_ADDRESS: Address = + address!("0000000000000000000000000000000000000000"); + +/// The L1 message queue v2 address for Scroll dev. +pub const SCROLL_DEV_L1_MESSAGE_QUEUE_V2_ADDRESS: Address = + address!("0000000000000000000000000000000000000000"); + +/// The L1 proxy address for Scroll dev. +pub const SCROLL_DEV_L1_PROXY_ADDRESS: Address = + address!("0000000000000000000000000000000000000000"); + +/// The maximum allowed l1 messages per block for Scroll dev. +pub const SCROLL_DEV_MAX_L1_MESSAGES: u64 = 10; + +/// The L1 configuration for Scroll dev. +pub const SCROLL_DEV_L1_CONFIG: L1Config = L1Config { + l1_chain_id: alloy_chains::NamedChain::Goerli as u64, + l1_message_queue_address: SCROLL_DEV_L1_MESSAGE_QUEUE_ADDRESS, + l1_message_queue_v2_address: SCROLL_DEV_L1_MESSAGE_QUEUE_V2_ADDRESS, + scroll_chain_address: SCROLL_DEV_L1_PROXY_ADDRESS, + l2_system_config_address: SCROLL_DEV_L2_SYSTEM_CONFIG_CONTRACT_ADDRESS, + num_l1_messages_per_block: SCROLL_DEV_MAX_L1_MESSAGES, +}; + +/// The Scroll Sepolia genesis hash +pub const SCROLL_SEPOLIA_GENESIS_HASH: B256 = + b256!("aa62d1a8b2bffa9e5d2368b63aae0d98d54928bd713125e3fd9e5c896c68592c"); + +/// The base fee params for Feynman. +pub const SCROLL_BASE_FEE_PARAMS_FEYNMAN: BaseFeeParams = BaseFeeParams::new( + SCROLL_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_FEYNMAN, + SCROLL_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER_FEYNMAN, +); + +/// The scroll EIP1559 max change denominator for Feynman. +pub const SCROLL_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_FEYNMAN: u128 = 8; + +/// The scroll EIP1559 default elasticity multiplier for Feynman. +pub const SCROLL_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER_FEYNMAN: u128 = 2;
diff --git reth/crates/scroll/chainspec/src/dev.rs scroll-reth/crates/scroll/chainspec/src/dev.rs new file mode 100644 index 0000000000000000000000000000000000000000..61bc0e52d62700352ed170fea8767e38209dd33a --- /dev/null +++ scroll-reth/crates/scroll/chainspec/src/dev.rs @@ -0,0 +1,41 @@ +//! Chain specification in dev mode for custom chain. + +use crate::{ + constants::SCROLL_BASE_FEE_PARAMS_FEYNMAN, make_genesis_header, LazyLock, ScrollChainConfig, + ScrollChainSpec, +}; +use alloc::{sync::Arc, vec}; + +use alloy_chains::Chain; +use alloy_primitives::U256; +use reth_chainspec::{BaseFeeParamsKind, ChainSpec, Hardfork}; +use reth_primitives_traits::SealedHeader; +use reth_scroll_forks::DEV_HARDFORKS; +use scroll_alloy_hardforks::ScrollHardfork; + +/// Scroll dev testnet specification +/// +/// Includes 20 prefunded accounts with `10_000` ETH each derived from mnemonic "test test test test +/// test test test test test test test junk". +pub static SCROLL_DEV: LazyLock<Arc<ScrollChainSpec>> = LazyLock::new(|| { + // In order to have Feynman activated at block 0, we set the `baseFeePerGas` field of the devnet + // genesis to 0. + let genesis = serde_json::from_str(include_str!("../res/genesis/dev.json")) + .expect("Can't deserialize Dev testnet genesis json"); + + ScrollChainSpec { + inner: ChainSpec { + chain: Chain::dev(), + genesis_header: SealedHeader::new_unhashed(make_genesis_header(&genesis)), + genesis, + paris_block_and_final_difficulty: Some((0, U256::from(0))), + hardforks: DEV_HARDFORKS.clone(), + base_fee_params: BaseFeeParamsKind::Variable( + vec![(ScrollHardfork::Feynman.boxed(), SCROLL_BASE_FEE_PARAMS_FEYNMAN)].into(), + ), + ..Default::default() + }, + config: ScrollChainConfig::dev(), + } + .into() +});
diff --git reth/crates/scroll/chainspec/src/genesis.rs scroll-reth/crates/scroll/chainspec/src/genesis.rs new file mode 100644 index 0000000000000000000000000000000000000000..c522461ec012362eb6315e5ee12b26dab11bb256 --- /dev/null +++ scroll-reth/crates/scroll/chainspec/src/genesis.rs @@ -0,0 +1,268 @@ +//! Scroll types for genesis data. + +use crate::{ + constants::{ + MAX_TX_PAYLOAD_BYTES_PER_BLOCK, SCROLL_FEE_VAULT_ADDRESS, SCROLL_MAINNET_L1_CONFIG, + SCROLL_SEPOLIA_L1_CONFIG, + }, + SCROLL_DEV_L1_CONFIG, +}; + +use alloy_primitives::Address; +use alloy_serde::OtherFields; +use serde::de::Error; + +/// Container type for all Scroll-specific fields in a genesis file. +/// This struct represents the configuration details and metadata +/// that are specific to the Scroll blockchain, used during the chain's initialization. +#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScrollChainInfo { + /// Information about hard forks specific to the Scroll chain. + /// This optional field contains metadata about various hard fork + /// configurations that are specific to the Scroll blockchain. + pub hard_fork_info: Option<ScrollHardforkInfo>, + /// Scroll chain-specific configuration details. + /// Encapsulates special parameters and settings + /// required for Scroll chain functionality, such as fee-related + /// addresses and Layer 1 configuration. + pub scroll_chain_config: ScrollChainConfig, +} + +impl ScrollChainInfo { + /// Extracts the Scroll specific fields from a genesis file. These fields are expected to be + /// contained in the `genesis.config` under `extra_fields` property. + pub fn extract_from(others: &OtherFields) -> Option<Self> { + Self::try_from(others).ok() + } +} + +impl TryFrom<&OtherFields> for ScrollChainInfo { + type Error = serde_json::Error; + + fn try_from(others: &OtherFields) -> Result<Self, Self::Error> { + let hard_fork_info = ScrollHardforkInfo::try_from(others).ok(); + let scroll_chain_config = ScrollChainConfig::try_from(others)?; + + Ok(Self { hard_fork_info, scroll_chain_config }) + } +} + +/// [`ScrollHardforkInfo`] specifies the block numbers and timestamps at which the Scroll hardforks +/// were activated. +#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScrollHardforkInfo { + /// archimedes block number + pub archimedes_block: Option<u64>, + /// bernoulli block number + pub bernoulli_block: Option<u64>, + /// curie block number + pub curie_block: Option<u64>, + /// darwin hardfork timestamp + pub darwin_time: Option<u64>, + /// darwinV2 hardfork timestamp + pub darwin_v2_time: Option<u64>, + /// euclid hardfork timestamp + pub euclid_time: Option<u64>, + /// euclidV2 hardfork timestamp + pub euclid_v2_time: Option<u64>, + /// feynman hardfork timestamp + pub feynman_time: Option<u64>, +} + +impl ScrollHardforkInfo { + /// Extract the Scroll-specific genesis info from a genesis file. + pub fn extract_from(others: &OtherFields) -> Option<Self> { + Self::try_from(others).ok() + } +} + +impl TryFrom<&OtherFields> for ScrollHardforkInfo { + type Error = serde_json::Error; + + fn try_from(others: &OtherFields) -> Result<Self, Self::Error> { + others.deserialize_as() + } +} + +/// The Scroll l1 config +#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct L1Config { + /// l1 chain id + pub l1_chain_id: u64, + /// The L1 contract address of the contract that handles the message queue targeting the Scroll + /// rollup. + pub l1_message_queue_address: Address, + /// The L1 contract address of the contract that handles the message queue v2 targeting the + /// Scroll rollup, used post Euclid fork. + pub l1_message_queue_v2_address: Address, + /// The L1 contract address of the proxy contract which is responsible for Scroll rollup + /// settlement. + pub scroll_chain_address: Address, + /// The address of the L2 system contract. + pub l2_system_config_address: Address, + /// The maximum number of L1 messages to be consumed per L2 rollup block. + pub num_l1_messages_per_block: u64, +} + +/// The configuration for the Scroll sequencer chain. +/// This struct holds the configuration details specific to the Scroll chain, +/// including fee-related addresses and L1 chain-specific settings. +#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScrollChainConfig { + /// The address of the L2 transaction fee vault. + /// This is an optional field that, when set, specifies where L2 transaction fees + /// will be sent or stored. + pub fee_vault_address: Option<Address>, + /// The maximum tx payload size of blocks that we produce. + pub max_tx_payload_bytes_per_block: usize, + /// The L1 configuration. + /// This field encapsulates specific settings and parameters required for L1 + pub l1_config: L1Config, +} + +impl ScrollChainConfig { + /// Extracts the scroll special info by looking for the `scroll` key. It is intended to be + /// parsed from a genesis file. + pub fn extract_from(others: &OtherFields) -> Option<Self> { + Self::try_from(others).ok() + } + + /// Returns the [`ScrollChainConfig`] for Scroll Mainnet. + pub const fn mainnet() -> Self { + Self { + fee_vault_address: Some(SCROLL_FEE_VAULT_ADDRESS), + max_tx_payload_bytes_per_block: MAX_TX_PAYLOAD_BYTES_PER_BLOCK, + l1_config: SCROLL_MAINNET_L1_CONFIG, + } + } + + /// Returns the [`ScrollChainConfig`] for Scroll Sepolia. + pub const fn sepolia() -> Self { + Self { + fee_vault_address: Some(SCROLL_FEE_VAULT_ADDRESS), + max_tx_payload_bytes_per_block: MAX_TX_PAYLOAD_BYTES_PER_BLOCK, + l1_config: SCROLL_SEPOLIA_L1_CONFIG, + } + } + + /// Returns the [`ScrollChainConfig`] for Scroll dev. + pub const fn dev() -> Self { + Self { + fee_vault_address: Some(SCROLL_FEE_VAULT_ADDRESS), + max_tx_payload_bytes_per_block: MAX_TX_PAYLOAD_BYTES_PER_BLOCK, + l1_config: SCROLL_DEV_L1_CONFIG, + } + } +} + +impl TryFrom<&OtherFields> for ScrollChainConfig { + type Error = serde_json::Error; + + fn try_from(others: &OtherFields) -> Result<Self, Self::Error> { + if let Some(Ok(scroll_chain_config)) = others.get_deserialized::<Self>("scroll") { + Ok(scroll_chain_config) + } else { + Err(serde_json::Error::missing_field("scroll")) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::SCROLL_MAINNET_L2_SYSTEM_CONFIG_CONTRACT_ADDRESS; + + use alloy_primitives::address; + + #[test] + fn test_extract_scroll_genesis_info() { + let genesis_info = r#" + { + "archimedesBlock": 0, + "bernoulliBlock": 10, + "curieBlock": 12, + "darwinTime": 0, + "euclidTime": 11, + "feynmanTime": 100 + } + "#; + + let others: OtherFields = serde_json::from_str(genesis_info).unwrap(); + let genesis_info = ScrollHardforkInfo::extract_from(&others).unwrap(); + + assert_eq!( + genesis_info, + ScrollHardforkInfo { + archimedes_block: Some(0), + bernoulli_block: Some(10), + curie_block: Some(12), + darwin_time: Some(0), + darwin_v2_time: None, + euclid_time: Some(11), + euclid_v2_time: None, + feynman_time: Some(100), + } + ); + } + + #[test] + fn test_extract_scroll_chain_info() { + let chain_info_str = r#" + { + "archimedesBlock": 0, + "bernoulliBlock": 10, + "curieBlock": 12, + "darwinTime": 0, + "euclidTime": 11, + "feynmanTime": 100, + "scroll": { + "feeVaultAddress": "0x5300000000000000000000000000000000000005", + "maxTxPayloadBytesPerBlock": 122880, + "l1Config": { + "l1ChainId": 1, + "l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B", + "l1MessageQueueV2Address": "0x56971da63A3C0205184FEF096E9ddFc7A8C2D18a", + "l2SystemConfigAddress": "0x331A873a2a85219863d80d248F9e2978fE88D0Ea", + "scrollChainAddress": "0xa13BAF47339d63B743e7Da8741db5456DAc1E556", + "numL1MessagesPerBlock": 10 + } + } + } + "#; + + let others: OtherFields = serde_json::from_str(chain_info_str).unwrap(); + let chain_info = ScrollChainInfo::extract_from(&others).unwrap(); + + let expected = ScrollChainInfo { + hard_fork_info: Some(ScrollHardforkInfo { + archimedes_block: Some(0), + bernoulli_block: Some(10), + curie_block: Some(12), + darwin_time: Some(0), + darwin_v2_time: None, + euclid_time: Some(11), + euclid_v2_time: None, + feynman_time: Some(100), + }), + scroll_chain_config: ScrollChainConfig { + fee_vault_address: Some(address!("5300000000000000000000000000000000000005")), + max_tx_payload_bytes_per_block: MAX_TX_PAYLOAD_BYTES_PER_BLOCK, + l1_config: L1Config { + l1_chain_id: 1, + l1_message_queue_address: address!("0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B"), + l1_message_queue_v2_address: address!( + "56971da63A3C0205184FEF096E9ddFc7A8C2D18a" + ), + l2_system_config_address: SCROLL_MAINNET_L2_SYSTEM_CONFIG_CONTRACT_ADDRESS, + scroll_chain_address: address!("a13BAF47339d63B743e7Da8741db5456DAc1E556"), + num_l1_messages_per_block: 10, + }, + }, + }; + assert_eq!(chain_info, expected); + } +}
diff --git reth/crates/scroll/chainspec/src/lib.rs scroll-reth/crates/scroll/chainspec/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..fea1e1efaca5b9bfba873ed825ad38d36cdea2b8 --- /dev/null +++ scroll-reth/crates/scroll/chainspec/src/lib.rs @@ -0,0 +1,793 @@ +//! Scroll-Reth chain specs. + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] + +use alloc::{boxed::Box, vec, vec::Vec}; +use alloy_chains::Chain; +use alloy_consensus::Header; +use alloy_genesis::Genesis; +use alloy_primitives::{B256, U256}; +use derive_more::{Constructor, Deref, From, Into}; +use reth_chainspec::{ + BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder, DepositContract, EthChainSpec, + EthereumCapabilities, EthereumHardforks, ForkFilter, ForkId, Hardforks, Head, +}; +use reth_ethereum_forks::{ + ChainHardforks, EthereumHardfork, ForkCondition, ForkFilterKey, ForkHash, Hardfork, +}; +use reth_network_peers::NodeRecord; +use reth_primitives_traits::SealedHeader; +use scroll_alloy_hardforks::{ScrollHardfork, ScrollHardforks}; + +use alloy_eips::eip7840::BlobParams; +#[cfg(not(feature = "std"))] +use once_cell::sync::Lazy as LazyLock; +#[cfg(feature = "std")] +use std::sync::LazyLock; + +extern crate alloc; + +mod constants; +pub use constants::{ + MAX_TX_PAYLOAD_BYTES_PER_BLOCK, SCROLL_BASE_FEE_PARAMS_FEYNMAN, SCROLL_DEV_L1_CONFIG, + SCROLL_DEV_L1_MESSAGE_QUEUE_ADDRESS, SCROLL_DEV_L1_MESSAGE_QUEUE_V2_ADDRESS, + SCROLL_DEV_L1_PROXY_ADDRESS, SCROLL_DEV_L2_SYSTEM_CONFIG_CONTRACT_ADDRESS, + SCROLL_DEV_MAX_L1_MESSAGES, SCROLL_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_FEYNMAN, + SCROLL_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER_FEYNMAN, SCROLL_FEE_VAULT_ADDRESS, + SCROLL_MAINNET_GENESIS_HASH, SCROLL_MAINNET_L1_CONFIG, SCROLL_MAINNET_L1_MESSAGE_QUEUE_ADDRESS, + SCROLL_MAINNET_L1_MESSAGE_QUEUE_V2_ADDRESS, SCROLL_MAINNET_L1_PROXY_ADDRESS, + SCROLL_MAINNET_L2_SYSTEM_CONFIG_CONTRACT_ADDRESS, SCROLL_MAINNET_MAX_L1_MESSAGES, + SCROLL_SEPOLIA_GENESIS_HASH, SCROLL_SEPOLIA_L1_CONFIG, SCROLL_SEPOLIA_L1_MESSAGE_QUEUE_ADDRESS, + SCROLL_SEPOLIA_L1_MESSAGE_QUEUE_V2_ADDRESS, SCROLL_SEPOLIA_L1_PROXY_ADDRESS, + SCROLL_SEPOLIA_L2_SYSTEM_CONFIG_CONTRACT_ADDRESS, SCROLL_SEPOLIA_MAX_L1_MESSAGES, +}; + +mod dev; +pub use dev::SCROLL_DEV; + +mod genesis; +pub use genesis::{ScrollChainConfig, ScrollChainInfo}; + +// convenience re-export of the chain spec provider. +pub use reth_chainspec::ChainSpecProvider; +use reth_scroll_forks::SCROLL_MAINNET_HARDFORKS; + +mod scroll; +pub use scroll::SCROLL_MAINNET; + +mod scroll_sepolia; +pub use scroll_sepolia::SCROLL_SEPOLIA; + +/// Chain spec builder for a Scroll chain. +#[derive(Debug, Default, From)] +pub struct ScrollChainSpecBuilder { + /// [`ChainSpecBuilder`] + inner: ChainSpecBuilder, +} + +impl ScrollChainSpecBuilder { + /// Construct a new builder from the scroll mainnet chain spec. + pub fn scroll_mainnet() -> Self { + Self { + inner: ChainSpecBuilder::default() + .chain(SCROLL_MAINNET.chain) + .genesis(SCROLL_MAINNET.genesis.clone()) + .with_forks(SCROLL_MAINNET.hardforks.clone()), + } + } + + /// Construct a new builder from the scroll sepolia chain spec. + pub fn scroll_sepolia() -> Self { + Self { + inner: ChainSpecBuilder::default() + .chain(SCROLL_SEPOLIA.chain) + .genesis(SCROLL_SEPOLIA.genesis.clone()) + .with_forks(SCROLL_SEPOLIA.hardforks.clone()), + } + } +} + +impl ScrollChainSpecBuilder { + /// Set the chain ID + pub fn chain(mut self, chain: Chain) -> Self { + self.inner = self.inner.chain(chain); + self + } + + /// Set the genesis block. + pub fn genesis(mut self, genesis: Genesis) -> Self { + self.inner = self.inner.genesis(genesis); + self + } + + /// Add the given fork with the given activation condition to the spec. + pub fn with_fork<H: Hardfork>(mut self, fork: H, condition: ForkCondition) -> Self { + self.inner = self.inner.with_fork(fork, condition); + self + } + + /// Add the given forks with the given activation condition to the spec. + pub fn with_forks(mut self, forks: ChainHardforks) -> Self { + self.inner = self.inner.with_forks(forks); + self + } + + /// Remove the given fork from the spec. + pub fn without_fork(mut self, fork: ScrollHardfork) -> Self { + self.inner = self.inner.without_fork(fork); + self + } + + /// Enable Archimedes at genesis + pub fn archimedes_activated(mut self) -> Self { + self.inner = self.inner.london_activated(); + self.inner = self.inner.with_fork(ScrollHardfork::Archimedes, ForkCondition::Block(0)); + self + } + + /// Enable Bernoulli at genesis + pub fn bernoulli_activated(mut self) -> Self { + self = self.archimedes_activated(); + self.inner = self.inner.with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(0)); + self.inner = self.inner.with_fork(ScrollHardfork::Bernoulli, ForkCondition::Block(0)); + self + } + + /// Enable Curie at genesis + pub fn curie_activated(mut self) -> Self { + self = self.bernoulli_activated(); + self.inner = self.inner.with_fork(ScrollHardfork::Curie, ForkCondition::Block(0)); + self + } + + /// Enable Darwin at genesis + pub fn darwin_activated(mut self) -> Self { + self = self.curie_activated(); + self.inner = self.inner.with_fork(ScrollHardfork::Darwin, ForkCondition::Timestamp(0)); + self + } + + /// Enable `DarwinV2` at genesis + pub fn darwin_v2_activated(mut self) -> Self { + self = self.darwin_activated(); + self.inner = self.inner.with_fork(ScrollHardfork::DarwinV2, ForkCondition::Timestamp(0)); + self + } + + /// Enable `Euclid` at genesis + pub fn euclid_activated(mut self) -> Self { + self = self.darwin_v2_activated(); + self.inner = self.inner.with_fork(ScrollHardfork::Euclid, ForkCondition::Timestamp(0)); + self + } + + /// Enable `EuclidV2` at genesis + pub fn euclid_v2_activated(mut self) -> Self { + self = self.euclid_activated(); + self.inner = self.inner.with_fork(ScrollHardfork::EuclidV2, ForkCondition::Timestamp(0)); + self + } + + /// Build the resulting [`ScrollChainSpec`]. + /// + /// # Panics + /// + /// This function panics if the chain ID and genesis is not set ([`Self::chain`] and + /// [`Self::genesis`]) + pub fn build(self, config: ScrollChainConfig) -> ScrollChainSpec { + ScrollChainSpec { inner: self.inner.build(), config } + } +} + +// Used by the CLI for custom genesis files. +impl ScrollChainSpec { + /// Build from a custom `Genesis`, ensuring: + /// - `genesis_header` has `base_fee_per_gas` (0 if Feynman@genesis) + /// - `base_fee_params` switch to Scroll defaults at Feynman + pub fn from_custom_genesis(genesis: Genesis) -> Self { + // Use the existing From<Genesis> as the base. + let mut spec: Self = genesis.into(); + + // Determine whether Feynman is active at genesis. + let feynman_active_at_genesis = + spec.is_feynman_active_at_timestamp(spec.inner.genesis.timestamp); + + // Ensure the genesis header has a base fee when required. + let mut header = make_genesis_header(&spec.inner.genesis); + if header.base_fee_per_gas.is_none() && feynman_active_at_genesis { + header.base_fee_per_gas = Some(0); + } + spec.inner.genesis_header = SealedHeader::new_unhashed(header); + + // Use Scroll's EIP-1559 params from Feynman onwards. + spec.inner.base_fee_params = BaseFeeParamsKind::Variable( + vec![(ScrollHardfork::Feynman.boxed(), SCROLL_BASE_FEE_PARAMS_FEYNMAN)].into(), + ); + spec + } +} + +/// Returns the chain configuration. +#[auto_impl::auto_impl(Arc)] +pub trait ChainConfig { + /// The configuration. + type Config; + + /// Returns the chain configuration. + fn chain_config(&self) -> &Self::Config; +} + +impl ChainConfig for ScrollChainSpec { + type Config = ScrollChainConfig; + + fn chain_config(&self) -> &Self::Config { + &self.config + } +} + +/// Scroll chain spec type. +#[derive(Debug, Clone, Deref, Into, Constructor, PartialEq, Eq)] +pub struct ScrollChainSpec { + /// [`ChainSpec`]. + #[deref] + pub inner: ChainSpec, + /// [`ScrollChainConfig`] + pub config: ScrollChainConfig, +} + +impl EthChainSpec for ScrollChainSpec { + type Header = Header; + + fn chain(&self) -> alloy_chains::Chain { + self.inner.chain() + } + + fn base_fee_params_at_block(&self, block_number: u64) -> BaseFeeParams { + self.inner.base_fee_params_at_block(block_number) + } + + fn base_fee_params_at_timestamp(&self, timestamp: u64) -> BaseFeeParams { + self.inner.base_fee_params_at_timestamp(timestamp) + } + + fn blob_params_at_timestamp(&self, timestamp: u64) -> Option<BlobParams> { + self.inner.blob_params_at_timestamp(timestamp) + } + + fn deposit_contract(&self) -> Option<&DepositContract> { + self.inner.deposit_contract() + } + + fn genesis_hash(&self) -> B256 { + self.inner.genesis_hash() + } + + fn prune_delete_limit(&self) -> usize { + self.inner.prune_delete_limit() + } + + fn display_hardforks(&self) -> Box<dyn alloc::fmt::Display> { + Box::new(ChainSpec::display_hardforks(self)) + } + + fn genesis_header(&self) -> &Header { + self.inner.genesis_header() + } + + fn genesis(&self) -> &Genesis { + self.inner.genesis() + } + + fn bootnodes(&self) -> Option<Vec<NodeRecord>> { + self.inner.bootnodes() + } + + fn final_paris_total_difficulty(&self) -> Option<U256> { + self.inner.final_paris_total_difficulty() + } +} + +impl EthereumCapabilities for ScrollChainSpec { + fn withdrawals_active(&self, _: u64) -> bool { + // Scroll doesn't activate withdrawals. + false + } +} + +fn make_genesis_header(genesis: &Genesis) -> Header { + Header { + gas_limit: genesis.gas_limit, + difficulty: genesis.difficulty, + nonce: genesis.nonce.into(), + extra_data: genesis.extra_data.clone(), + state_root: reth_trie_common::root::state_root_ref_unhashed(&genesis.alloc), + timestamp: genesis.timestamp, + mix_hash: genesis.mix_hash, + beneficiary: genesis.coinbase, + base_fee_per_gas: genesis + .base_fee_per_gas + .map(|b| b.try_into().expect("base fee should fit in u64")), + withdrawals_root: None, + parent_beacon_block_root: None, + blob_gas_used: None, + excess_blob_gas: None, + requests_hash: None, + ..Default::default() + } +} + +impl Hardforks for ScrollChainSpec { + fn fork<H: Hardfork>(&self, fork: H) -> ForkCondition { + self.inner.fork(fork) + } + + fn forks_iter(&self) -> impl Iterator<Item = (&dyn Hardfork, ForkCondition)> { + self.inner.forks_iter() + } + + fn fork_id(&self, head: &Head) -> ForkId { + // TODO: Geth does not support time based hard forks for its `ForkID` calculation. As such, + // we are only using block based hard forks for now. + // self.inner.fork_id(head) + + // The following code is modified version of self.inner.fork_id(head) to ignore time based + // hard forks. + let mut forkhash = ForkHash::from(self.inner.genesis_hash()); + let mut current_applied = 0; + // handle all block forks before handling timestamp based forks. see: https://eips.ethereum.org/EIPS/eip-6122 + for (_, cond) in self.hardforks.forks_iter() { + // handle block based forks and the sepolia merge netsplit block edge case (TTD + // ForkCondition with Some(block)) + if let ForkCondition::Block(block) | + ForkCondition::TTD { fork_block: Some(block), .. } = cond + { + if head.number >= block { + // skip duplicated hardforks: hardforks enabled at genesis block + if block != current_applied { + forkhash += block; + current_applied = block; + } + } else { + // we can return here because this block fork is not active, so we set the + // `next` value + return ForkId { hash: forkhash, next: block } + } + } + } + ForkId { hash: forkhash, next: 0 } + } + + fn latest_fork_id(&self) -> ForkId { + self.inner.latest_fork_id() + } + + fn fork_filter(&self, head: Head) -> ForkFilter { + let forks = self.inner.hardforks.forks_iter().filter_map(|(_, condition)| { + // We filter out TTD-based forks w/o a pre-known block since those do not show up in the + // fork filter. + Some(match condition { + ForkCondition::Block(block) | + ForkCondition::TTD { fork_block: Some(block), .. } => ForkFilterKey::Block(block), + _ => return None, + }) + }); + + ForkFilter::new(head, self.genesis_hash(), self.genesis_timestamp(), forks) + } +} + +impl EthereumHardforks for ScrollChainSpec { + fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition { + self.fork(fork) + } +} + +impl ScrollHardforks for ScrollChainSpec { + fn scroll_fork_activation(&self, fork: ScrollHardfork) -> ForkCondition { + self.fork(fork) + } +} + +impl From<ChainSpec> for ScrollChainSpec { + fn from(value: ChainSpec) -> Self { + let genesis = value.genesis; + genesis.into() + } +} + +impl From<Genesis> for ScrollChainSpec { + fn from(genesis: Genesis) -> Self { + let scroll_chain_info = ScrollConfigInfo::extract_from(&genesis); + let hard_fork_info = + scroll_chain_info.scroll_chain_info.hard_fork_info.expect("load scroll hard fork info"); + + // Block-based hardforks + let hardfork_opts = [ + (EthereumHardfork::Homestead.boxed(), genesis.config.homestead_block), + (EthereumHardfork::Tangerine.boxed(), genesis.config.eip150_block), + (EthereumHardfork::SpuriousDragon.boxed(), genesis.config.eip155_block), + (EthereumHardfork::Byzantium.boxed(), genesis.config.byzantium_block), + (EthereumHardfork::Constantinople.boxed(), genesis.config.constantinople_block), + (EthereumHardfork::Petersburg.boxed(), genesis.config.petersburg_block), + (EthereumHardfork::Istanbul.boxed(), genesis.config.istanbul_block), + (EthereumHardfork::Berlin.boxed(), genesis.config.berlin_block), + (EthereumHardfork::London.boxed(), genesis.config.london_block), + (ScrollHardfork::Archimedes.boxed(), hard_fork_info.archimedes_block), + (ScrollHardfork::Bernoulli.boxed(), hard_fork_info.bernoulli_block), + (ScrollHardfork::Curie.boxed(), hard_fork_info.curie_block), + ]; + let mut block_hardforks = hardfork_opts + .into_iter() + .filter_map(|(hardfork, opt)| opt.map(|block| (hardfork, ForkCondition::Block(block)))) + .collect::<Vec<_>>(); + + // Time-based hardforks + let time_hardfork_opts = [ + (EthereumHardfork::Shanghai.boxed(), genesis.config.shanghai_time), + (ScrollHardfork::Darwin.boxed(), hard_fork_info.darwin_time), + (ScrollHardfork::DarwinV2.boxed(), hard_fork_info.darwin_v2_time), + (ScrollHardfork::Euclid.boxed(), hard_fork_info.euclid_time), + (ScrollHardfork::EuclidV2.boxed(), hard_fork_info.euclid_v2_time), + (ScrollHardfork::Feynman.boxed(), hard_fork_info.feynman_time), + ]; + + let mut time_hardforks = time_hardfork_opts + .into_iter() + .filter_map(|(hardfork, opt)| { + opt.map(|time| (hardfork, ForkCondition::Timestamp(time))) + }) + .collect::<Vec<_>>(); + + block_hardforks.append(&mut time_hardforks); + + // Ordered Hardforks + let mainnet_hardforks = SCROLL_MAINNET_HARDFORKS.clone(); + let mainnet_order = mainnet_hardforks.forks_iter(); + + let mut ordered_hardforks = Vec::with_capacity(block_hardforks.len()); + for (hardfork, _) in mainnet_order { + if let Some(pos) = block_hardforks.iter().position(|(e, _)| **e == *hardfork) { + ordered_hardforks.push(block_hardforks.remove(pos)); + } + } + + // append the remaining unknown hardforks to ensure we don't filter any out + ordered_hardforks.append(&mut block_hardforks); + + Self { + inner: ChainSpec { + chain: genesis.config.chain_id.into(), + genesis, + hardforks: ChainHardforks::new(ordered_hardforks), + ..Default::default() + }, + config: scroll_chain_info.scroll_chain_info.scroll_chain_config, + } + } +} + +#[derive(Default, Debug)] +struct ScrollConfigInfo { + scroll_chain_info: ScrollChainInfo, +} + +impl ScrollConfigInfo { + fn extract_from(genesis: &Genesis) -> Self { + Self { + scroll_chain_info: ScrollChainInfo::extract_from(&genesis.config.extra_fields) + .expect("extract scroll extra fields failed"), + } + } +} + +#[cfg(test)] +mod tests { + use crate::*; + use alloy_genesis::{ChainConfig, Genesis}; + use alloy_primitives::b256; + use reth_chainspec::{test_fork_ids, ForkFilterKey}; + use reth_ethereum_forks::{EthereumHardfork, ForkHash}; + + #[test] + fn scroll_mainnet_genesis_hash() { + let scroll_mainnet = + ScrollChainSpecBuilder::scroll_mainnet().build(ScrollChainConfig::mainnet()); + assert_eq!( + b256!("908789cb20d00fc6070093f142aa8d02c21cfb0a9b9cfd4621d8cf0255234c0f"), + scroll_mainnet.genesis_hash() + ); + } + + #[test] + fn scroll_sepolia_genesis_hash() { + let scroll_sepolia = + ScrollChainSpecBuilder::scroll_sepolia().build(ScrollChainConfig::sepolia()); + assert_eq!( + b256!("04414a71425e8ef2632e99a4b148c69d69bab8ffa47ee814231331a33d073df2"), + scroll_sepolia.genesis_hash() + ); + } + + #[test] + fn scroll_mainnet_forkids_deref() { + test_fork_ids( + &SCROLL_MAINNET, + &[ + ( + Head { number: 0, ..Default::default() }, + ForkId { hash: ForkHash([0xea, 0x6b, 0x56, 0xca]), next: 5220340 }, + ), + ( + Head { number: 5220340, ..Default::default() }, + ForkId { hash: ForkHash([0xee, 0x46, 0xae, 0x2a]), next: 7096836 }, + ), + ( + Head { number: 7096836, ..Default::default() }, + ForkId { hash: ForkHash([0x18, 0xd3, 0xc8, 0xd9]), next: 1724227200 }, + ), + ( + Head { number: 7096836, timestamp: 1724227200, ..Default::default() }, + ForkId { hash: ForkHash([0xcc, 0xeb, 0x09, 0xb0]), next: 1725264000 }, + ), + ( + Head { number: 7096836, timestamp: 1725264000, ..Default::default() }, + ForkId { hash: ForkHash([0x21, 0xa2, 0x07, 0x54]), next: 1744815600 }, + ), + ( + Head { number: 7096836, timestamp: 1744815600, ..Default::default() }, + ForkId { hash: ForkHash([0xca, 0xc5, 0x80, 0xca]), next: 1745305200 }, + ), + ( + Head { number: 7096836, timestamp: 1745305200, ..Default::default() }, + ForkId { hash: ForkHash([0x0e, 0xcf, 0xb2, 0x31]), next: 1755576000 }, + ), + ( + Head { number: 7096836, timestamp: 1755576000, ..Default::default() }, + ForkId { hash: ForkHash([0x38, 0x0f, 0x78, 0x5d]), next: 0 }, + ), + ], + ); + } + + #[test] + fn scroll_mainnet_forkids() { + let cases = [ + ( + Head { number: 0, ..Default::default() }, + ForkId { hash: ForkHash([0xea, 0x6b, 0x56, 0xca]), next: 5220340 }, + ), + ( + Head { number: 5220340, ..Default::default() }, + ForkId { hash: ForkHash([0xee, 0x46, 0xae, 0x2a]), next: 7096836 }, + ), + ( + Head { number: 7096836, ..Default::default() }, + ForkId { hash: ForkHash([0x18, 0xd3, 0xc8, 0xd9]), next: 0 }, + ), + ]; + + for (block, expected_id) in cases { + let computed_id = SCROLL_MAINNET.fork_id(&block); + assert_eq!( + expected_id, computed_id, + "Expected fork ID {:?}, computed fork ID {:?} at block {}", + expected_id, computed_id, block.number + ); + } + } + + #[test] + fn scroll_mainnet_fork_filter_excludes_time_based_forks() { + let head = Default::default(); + let fork_filter = SCROLL_MAINNET.fork_filter(head); + + let forks = vec![ + ForkFilterKey::Block(0), + ForkFilterKey::Block(5220340), + ForkFilterKey::Block(7096836), + ]; + let expected_fork_filter = ForkFilter::new( + head, + SCROLL_MAINNET.genesis_hash(), + SCROLL_MAINNET.genesis_timestamp(), + forks, + ); + + assert_eq!(fork_filter, expected_fork_filter); + } + + #[test] + fn scroll_sepolia_forkids() { + test_fork_ids( + &SCROLL_SEPOLIA, + &[ + ( + Head { number: 0, ..Default::default() }, + ForkId { hash: ForkHash([0x25, 0xfa, 0xe4, 0x54]), next: 3747132 }, + ), + ( + Head { number: 3747132, ..Default::default() }, + ForkId { hash: ForkHash([0xda, 0x76, 0xc2, 0x2d]), next: 4740239 }, + ), + ( + Head { number: 4740239, ..Default::default() }, + ForkId { hash: ForkHash([0x9f, 0xb4, 0x75, 0xf1]), next: 1723622400 }, + ), + ( + Head { number: 4740239, timestamp: 1723622400, ..Default::default() }, + ForkId { hash: ForkHash([0xe9, 0x26, 0xd4, 0x9b]), next: 1724832000 }, + ), + ( + Head { number: 4740239, timestamp: 1724832000, ..Default::default() }, + ForkId { hash: ForkHash([0x69, 0xf3, 0x7e, 0xde]), next: 1741680000 }, + ), + ( + Head { number: 4740239, timestamp: 1741680000, ..Default::default() }, + ForkId { hash: ForkHash([0xf7, 0xac, 0x7e, 0xfc]), next: 1741852800 }, + ), + ( + Head { number: 4740239, timestamp: 1741852800, ..Default::default() }, + ForkId { hash: ForkHash([0x51, 0x7e, 0x0f, 0x1c]), next: 1753167600 }, + ), + ( + Head { number: 4740239, timestamp: 1753167600, ..Default::default() }, + ForkId { hash: ForkHash([0x19, 0xbb, 0x92, 0xc6]), next: 0 }, + ), + ], + ); + } + + #[test] + fn is_bernoulli_active() { + let scroll_mainnet = + ScrollChainSpecBuilder::scroll_mainnet().build(ScrollChainConfig::mainnet()); + assert!(!scroll_mainnet.is_bernoulli_active_at_block(1)) + } + + #[test] + fn parse_scroll_hardforks() { + let geth_genesis = r#" + { + "config": { + "bernoulliBlock": 10, + "curieBlock": 20, + "darwinTime": 30, + "darwinV2Time": 31, + "scroll": { + "feeVaultAddress": "0x5300000000000000000000000000000000000005", + "maxTxPayloadBytesPerBlock": 122880, + "l1Config": { + "l1ChainId": 1, + "l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B", + "l1MessageQueueV2Address": "0x56971da63A3C0205184FEF096E9ddFc7A8C2D18a", + "l2SystemConfigAddress": "0x331A873a2a85219863d80d248F9e2978fE88D0Ea", + "scrollChainAddress": "0xa13BAF47339d63B743e7Da8741db5456DAc1E556", + "numL1MessagesPerBlock": 10 + } + } + } + }"#; + let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap(); + + let actual_bernoulli_block = genesis.config.extra_fields.get("bernoulliBlock"); + assert_eq!(actual_bernoulli_block, Some(serde_json::Value::from(10)).as_ref()); + let actual_curie_block = genesis.config.extra_fields.get("curieBlock"); + assert_eq!(actual_curie_block, Some(serde_json::Value::from(20)).as_ref()); + let actual_darwin_timestamp = genesis.config.extra_fields.get("darwinTime"); + assert_eq!(actual_darwin_timestamp, Some(serde_json::Value::from(30)).as_ref()); + let actual_darwin_v2_timestamp = genesis.config.extra_fields.get("darwinV2Time"); + assert_eq!(actual_darwin_v2_timestamp, Some(serde_json::Value::from(31)).as_ref()); + let scroll_object = genesis.config.extra_fields.get("scroll").unwrap(); + assert_eq!( + scroll_object, + &serde_json::json!({ + "feeVaultAddress": "0x5300000000000000000000000000000000000005", + "maxTxPayloadBytesPerBlock": 122880, + "l1Config": { + "l1ChainId": 1, + "l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B", + "l1MessageQueueV2Address": "0x56971da63A3C0205184FEF096E9ddFc7A8C2D18a", + "l2SystemConfigAddress": "0x331A873a2a85219863d80d248F9e2978fE88D0Ea", + "scrollChainAddress": "0xa13BAF47339d63B743e7Da8741db5456DAc1E556", + "numL1MessagesPerBlock": 10 + } + }) + ); + + let chain_spec: ScrollChainSpec = genesis.into(); + + assert!(!chain_spec.is_fork_active_at_block(ScrollHardfork::Bernoulli, 0)); + assert!(!chain_spec.is_fork_active_at_block(ScrollHardfork::Curie, 0)); + assert!(!chain_spec.is_fork_active_at_timestamp(ScrollHardfork::Darwin, 0)); + assert!(!chain_spec.is_fork_active_at_timestamp(ScrollHardfork::DarwinV2, 0)); + + assert!(chain_spec.is_fork_active_at_block(ScrollHardfork::Bernoulli, 10)); + assert!(chain_spec.is_fork_active_at_block(ScrollHardfork::Curie, 20)); + assert!(chain_spec.is_fork_active_at_timestamp(ScrollHardfork::Darwin, 30)); + assert!(chain_spec.is_fork_active_at_timestamp(ScrollHardfork::DarwinV2, 31)); + } + + #[test] + fn test_fork_order_scroll_mainnet() { + let genesis = Genesis { + config: ChainConfig { + chain_id: 0, + homestead_block: Some(0), + dao_fork_block: Some(0), + dao_fork_support: false, + eip150_block: Some(0), + eip155_block: Some(0), + eip158_block: Some(0), + byzantium_block: Some(0), + constantinople_block: Some(0), + petersburg_block: Some(0), + istanbul_block: Some(0), + berlin_block: Some(0), + london_block: Some(0), + shanghai_time: Some(0), + extra_fields: [ + (String::from("archimedesBlock"), 0.into()), + (String::from("bernoulliBlock"), 0.into()), + (String::from("curieBlock"), 0.into()), + (String::from("darwinTime"), 0.into()), + (String::from("darwinV2Time"), 0.into()), + (String::from("feynmanTime"), 0.into()), + ( + String::from("scroll"), + serde_json::json!({ + "feeVaultAddress": "0x5300000000000000000000000000000000000005", + "maxTxPayloadBytesPerBlock": 122880, + "l1Config": { + "l1ChainId": 1, + "l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B", + "l1MessageQueueV2Address": "0x56971da63A3C0205184FEF096E9ddFc7A8C2D18a", + "l2SystemConfigAddress": "0x331A873a2a85219863d80d248F9e2978fE88D0Ea", + "scrollChainAddress": "0xa13BAF47339d63B743e7Da8741db5456DAc1E556", + "numL1MessagesPerBlock": 10 + } + }), + ), + ] + .into_iter() + .collect(), + ..Default::default() + }, + ..Default::default() + }; + + let chain_spec: ScrollChainSpec = genesis.into(); + + let hardforks: Vec<_> = chain_spec.hardforks.forks_iter().map(|(h, _)| h).collect(); + let expected_hardforks = vec![ + EthereumHardfork::Homestead.boxed(), + EthereumHardfork::Tangerine.boxed(), + EthereumHardfork::SpuriousDragon.boxed(), + EthereumHardfork::Byzantium.boxed(), + EthereumHardfork::Constantinople.boxed(), + EthereumHardfork::Petersburg.boxed(), + EthereumHardfork::Istanbul.boxed(), + EthereumHardfork::Berlin.boxed(), + EthereumHardfork::London.boxed(), + ScrollHardfork::Archimedes.boxed(), + EthereumHardfork::Shanghai.boxed(), + ScrollHardfork::Bernoulli.boxed(), + ScrollHardfork::Curie.boxed(), + ScrollHardfork::Darwin.boxed(), + ScrollHardfork::DarwinV2.boxed(), + ScrollHardfork::Feynman.boxed(), + ]; + + assert!(expected_hardforks + .iter() + .zip(hardforks.iter()) + .all(|(expected, actual)| &**expected == *actual)); + + assert_eq!(expected_hardforks.len(), hardforks.len()); + } +}
diff --git reth/crates/scroll/chainspec/src/scroll.rs scroll-reth/crates/scroll/chainspec/src/scroll.rs new file mode 100644 index 0000000000000000000000000000000000000000..d3f1ea2c0af40538e8a52e4a16ff64ce6e342df9 --- /dev/null +++ scroll-reth/crates/scroll/chainspec/src/scroll.rs @@ -0,0 +1,37 @@ +//! Chain specification for the Scroll Mainnet network. + +use crate::{ + constants::SCROLL_BASE_FEE_PARAMS_FEYNMAN, make_genesis_header, LazyLock, ScrollChainConfig, + ScrollChainSpec, SCROLL_MAINNET_GENESIS_HASH, +}; +use alloc::{sync::Arc, vec}; + +use alloy_chains::{Chain, NamedChain}; +use reth_chainspec::{BaseFeeParamsKind, ChainSpec, Hardfork}; +use reth_primitives_traits::SealedHeader; +use reth_scroll_forks::SCROLL_MAINNET_HARDFORKS; +use scroll_alloy_hardforks::ScrollHardfork; + +/// The Scroll Mainnet spec +pub static SCROLL_MAINNET: LazyLock<Arc<ScrollChainSpec>> = LazyLock::new(|| { + let genesis = serde_json::from_str(include_str!("../res/genesis/scroll.json")) + .expect("Can't deserialize Scroll Mainnet genesis json"); + ScrollChainSpec { + inner: ChainSpec { + // TODO(scroll): migrate to Chain::scroll() (introduced in https://github.com/alloy-rs/chains/pull/112) when alloy-chains is bumped to version 0.1.48 + chain: Chain::from_named(NamedChain::Scroll), + genesis_header: SealedHeader::new( + make_genesis_header(&genesis), + SCROLL_MAINNET_GENESIS_HASH, + ), + genesis, + hardforks: SCROLL_MAINNET_HARDFORKS.clone(), + base_fee_params: BaseFeeParamsKind::Variable( + vec![(ScrollHardfork::Feynman.boxed(), SCROLL_BASE_FEE_PARAMS_FEYNMAN)].into(), + ), + ..Default::default() + }, + config: ScrollChainConfig::mainnet(), + } + .into() +});
diff --git reth/crates/scroll/chainspec/src/scroll_sepolia.rs scroll-reth/crates/scroll/chainspec/src/scroll_sepolia.rs new file mode 100644 index 0000000000000000000000000000000000000000..2c152fff5f58ec56b82db9f45cfaf0d6accdb908 --- /dev/null +++ scroll-reth/crates/scroll/chainspec/src/scroll_sepolia.rs @@ -0,0 +1,37 @@ +//! Chain specification for the Scroll Sepolia testnet network. + +use crate::{ + constants::SCROLL_BASE_FEE_PARAMS_FEYNMAN, make_genesis_header, LazyLock, ScrollChainConfig, + ScrollChainSpec, SCROLL_SEPOLIA_GENESIS_HASH, +}; +use alloc::{sync::Arc, vec}; + +use alloy_chains::{Chain, NamedChain}; +use reth_chainspec::{BaseFeeParamsKind, ChainSpec, Hardfork}; +use reth_primitives_traits::SealedHeader; +use reth_scroll_forks::SCROLL_SEPOLIA_HARDFORKS; +use scroll_alloy_hardforks::ScrollHardfork; + +/// The Scroll Sepolia spec +pub static SCROLL_SEPOLIA: LazyLock<Arc<ScrollChainSpec>> = LazyLock::new(|| { + let genesis = serde_json::from_str(include_str!("../res/genesis/sepolia_scroll.json")) + .expect("Can't deserialize Scroll Sepolia genesis json"); + ScrollChainSpec { + inner: ChainSpec { + // TODO(scroll): migrate to Chain::scroll_sepolia() (introduced in https://github.com/alloy-rs/chains/pull/112) when alloy-chains is bumped to version 0.1.48 + chain: Chain::from_named(NamedChain::ScrollSepolia), + genesis_header: SealedHeader::new( + make_genesis_header(&genesis), + SCROLL_SEPOLIA_GENESIS_HASH, + ), + genesis, + hardforks: SCROLL_SEPOLIA_HARDFORKS.clone(), + base_fee_params: BaseFeeParamsKind::Variable( + vec![(ScrollHardfork::Feynman.boxed(), SCROLL_BASE_FEE_PARAMS_FEYNMAN)].into(), + ), + ..Default::default() + }, + config: ScrollChainConfig::sepolia(), + } + .into() +});
diff --git reth/crates/scroll/cli/Cargo.toml scroll-reth/crates/scroll/cli/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..d2dd4e51eea33acfcdc952c3f66dc0222a3eca6c --- /dev/null +++ scroll-reth/crates/scroll/cli/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "reth-scroll-cli" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# reth +reth-db = { workspace = true, features = ["scroll-alloy-traits"] } +reth-cli.workspace = true +reth-cli-commands.workspace = true +reth-cli-runner.workspace = true +reth-consensus.workspace = true +reth-node-builder.workspace = true +reth-node-core.workspace = true +reth-node-metrics.workspace = true +reth-tracing.workspace = true + +# scroll +reth-scroll-chainspec.workspace = true +reth-scroll-consensus.workspace = true +reth-scroll-evm.workspace = true +reth-scroll-node.workspace = true +reth-scroll-primitives = { workspace = true, features = ["reth-codec"] } +scroll-alloy-consensus = { workspace = true, optional = true } + +# misc +eyre.workspace = true +clap.workspace = true +proptest = { workspace = true, optional = true } +tracing.workspace = true + +[features] +dev = [ + "dep:proptest", + "dep:scroll-alloy-consensus", + "reth-cli-commands/arbitrary", +]
diff --git reth/crates/scroll/cli/src/app.rs scroll-reth/crates/scroll/cli/src/app.rs new file mode 100644 index 0000000000000000000000000000000000000000..fc6caf338443ee9032c05db19e1941c232a65c81 --- /dev/null +++ scroll-reth/crates/scroll/cli/src/app.rs @@ -0,0 +1,115 @@ +use crate::{Cli, Commands}; +use eyre::{eyre, Result}; +use reth_cli::chainspec::ChainSpecParser; +use reth_cli_commands::launcher::Launcher; +use reth_cli_runner::CliRunner; +use reth_node_metrics::recorder::install_prometheus_recorder; +use reth_scroll_chainspec::ScrollChainSpec; +use reth_scroll_consensus::ScrollBeaconConsensus; +use reth_scroll_evm::ScrollExecutorProvider; +use reth_scroll_node::ScrollNode; +use reth_tracing::{FileWorkerGuard, Layers}; +use std::{fmt, sync::Arc}; +use tracing::info; + +/// A wrapper around a parsed CLI that handles command execution. +#[derive(Debug)] +pub struct CliApp<Spec: ChainSpecParser, Ext: clap::Args + fmt::Debug> { + cli: Cli<Spec, Ext>, + runner: Option<CliRunner>, + layers: Option<Layers>, + guard: Option<FileWorkerGuard>, +} + +impl<C, Ext> CliApp<C, Ext> +where + C: ChainSpecParser<ChainSpec = ScrollChainSpec>, + Ext: clap::Args + fmt::Debug, +{ + pub(crate) fn new(cli: Cli<C, Ext>) -> Self { + Self { cli, runner: None, layers: Some(Layers::new()), guard: None } + } + + /// Sets the runner for the CLI commander. + /// + /// This replaces any existing runner with the provided one. + pub fn set_runner(&mut self, runner: CliRunner) { + self.runner = Some(runner); + } + + /// Access to tracing layers. + /// + /// Returns a mutable reference to the tracing layers, or error + /// if tracing initialized and layers have detached already. + pub fn access_tracing_layers(&mut self) -> Result<&mut Layers> { + self.layers.as_mut().ok_or_else(|| eyre!("Tracing already initialized")) + } + + /// Execute the configured cli command. + /// + /// This accepts a closure that is used to launch the node via the + /// [`NodeCommand`](reth_cli_commands::node::NodeCommand). + pub fn run(mut self, launcher: impl Launcher<C, Ext>) -> Result<()> { + let runner = match self.runner.take() { + Some(runner) => runner, + None => CliRunner::try_default_runtime()?, + }; + + // add network name to logs dir + // Add network name if available to the logs dir + if let Some(chain_spec) = self.cli.command.chain_spec() { + self.cli.logs.log_file_directory = + self.cli.logs.log_file_directory.join(chain_spec.chain.to_string()); + } + + self.init_tracing()?; + + // Install the prometheus recorder to be sure to record all metrics + let _ = install_prometheus_recorder(); + + let components = |spec: Arc<ScrollChainSpec>| { + (ScrollExecutorProvider::scroll(spec.clone()), ScrollBeaconConsensus::new(spec)) + }; + + match self.cli.command { + Commands::Node(command) => { + runner.run_command_until_exit(|ctx| command.execute(ctx, launcher)) + } + Commands::Import(command) => { + runner.run_blocking_until_ctrl_c(command.execute::<ScrollNode, _>(components)) + } + Commands::Init(command) => { + runner.run_blocking_until_ctrl_c(command.execute::<ScrollNode>()) + } + Commands::InitState(command) => { + runner.run_blocking_until_ctrl_c(command.execute::<ScrollNode>()) + } + Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()), + Commands::Db(command) => { + runner.run_blocking_until_ctrl_c(command.execute::<ScrollNode>()) + } + Commands::Stage(command) => runner + .run_command_until_exit(|ctx| command.execute::<ScrollNode, _>(ctx, components)), + Commands::P2P(command) => runner.run_until_ctrl_c(command.execute::<ScrollNode>()), + Commands::Config(command) => runner.run_until_ctrl_c(command.execute()), + Commands::Recover(command) => { + runner.run_command_until_exit(|ctx| command.execute::<ScrollNode>(ctx)) + } + Commands::Prune(command) => runner.run_until_ctrl_c(command.execute::<ScrollNode>()), + #[cfg(feature = "dev")] + Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()), + } + } + + /// Initializes tracing with the configured options. + /// + /// If file logging is enabled, this function stores guard to the struct. + pub fn init_tracing(&mut self) -> Result<()> { + if self.guard.is_none() { + let layers = self.layers.take().unwrap_or_default(); + self.guard = self.cli.logs.init_tracing_with_layers(layers)?; + info!(target: "reth::cli", "Initialized tracing, debug log directory: {}", self.cli.logs.log_file_directory); + } + Ok(()) + } +}
diff --git reth/crates/scroll/cli/src/commands/mod.rs scroll-reth/crates/scroll/cli/src/commands/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..1bd4c500164c8355801b267a6c6fe0c0031adefd --- /dev/null +++ scroll-reth/crates/scroll/cli/src/commands/mod.rs @@ -0,0 +1,80 @@ +#[cfg(feature = "dev")] +mod test_vectors; + +use crate::ScrollChainSpecParser; +use clap::Subcommand; +use reth_cli::chainspec::ChainSpecParser; +use reth_cli_commands::{ + config_cmd, db, dump_genesis, import, init_cmd, init_state, node, node::NoArgs, p2p, prune, + recover, stage, +}; +use reth_scroll_chainspec::ScrollChainSpec; +use std::{fmt, sync::Arc}; + +/// Commands to be executed +#[derive(Debug, Subcommand)] +#[allow(clippy::large_enum_variant)] +pub enum Commands< + Spec: ChainSpecParser = ScrollChainSpecParser, + Ext: clap::Args + fmt::Debug = NoArgs, +> { + /// Start the node + #[command(name = "node")] + Node(Box<node::NodeCommand<Spec, Ext>>), + /// Initialize the database from a genesis file. + #[command(name = "init")] + Init(init_cmd::InitCommand<Spec>), + /// Initialize the database from a state dump file. + #[command(name = "init-state")] + InitState(init_state::InitStateCommand<Spec>), + /// This syncs RLP encoded blocks from a file. + #[command(name = "import")] + Import(import::ImportCommand<Spec>), + /// Dumps genesis block JSON configuration to stdout. + DumpGenesis(dump_genesis::DumpGenesisCommand<Spec>), + /// Database debugging utilities + #[command(name = "db")] + Db(db::Command<Spec>), + /// Manipulate individual stages. + #[command(name = "stage")] + Stage(Box<stage::Command<Spec>>), + /// P2P Debugging utilities + #[command(name = "p2p")] + P2P(p2p::Command<Spec>), + /// Write config to stdout + #[command(name = "config")] + Config(config_cmd::Command), + /// Scripts for node recovery + #[command(name = "recover")] + Recover(recover::Command<Spec>), + /// Prune according to the configuration without any limits + #[command(name = "prune")] + Prune(prune::PruneCommand<Spec>), + /// Generate Test Vectors + #[cfg(feature = "dev")] + #[command(name = "test-vectors")] + TestVectors(test_vectors::Command), +} + +impl<C: ChainSpecParser<ChainSpec = ScrollChainSpec>, Ext: clap::Args + fmt::Debug> + Commands<C, Ext> +{ + /// Returns the underlying chain being used for commands + pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> { + match self { + Self::Node(cmd) => cmd.chain_spec(), + Self::Init(cmd) => cmd.chain_spec(), + Self::InitState(cmd) => cmd.chain_spec(), + Self::Import(cmd) => cmd.chain_spec(), + Self::DumpGenesis(cmd) => cmd.chain_spec(), + Self::Db(cmd) => cmd.chain_spec(), + Self::Stage(cmd) => cmd.chain_spec(), + Self::P2P(cmd) => cmd.chain_spec(), + Self::Config(_) => None, + Self::Recover(cmd) => cmd.chain_spec(), + Self::Prune(cmd) => cmd.chain_spec(), + #[cfg(feature = "dev")] + Self::TestVectors(_) => None, + } + } +}
diff --git reth/crates/scroll/cli/src/commands/test_vectors.rs scroll-reth/crates/scroll/cli/src/commands/test_vectors.rs new file mode 100644 index 0000000000000000000000000000000000000000..f48c0f0e2e286aa39333ada95107a5450e6b73f1 --- /dev/null +++ scroll-reth/crates/scroll/cli/src/commands/test_vectors.rs @@ -0,0 +1,72 @@ +//! Command for generating test vectors. + +use clap::{Parser, Subcommand}; +use proptest::test_runner::TestRunner; +use reth_cli_commands::{ + compact_types, + test_vectors::{ + compact, + compact::{ + generate_vector, read_vector, GENERATE_VECTORS as ETH_GENERATE_VECTORS, + READ_VECTORS as ETH_READ_VECTORS, + }, + tables, + }, +}; +use scroll_alloy_consensus::TxL1Message; + +/// Generate test-vectors for different data types. +#[derive(Debug, Parser)] +pub struct Command { + #[command(subcommand)] + command: Subcommands, +} + +#[derive(Subcommand, Debug)] +/// `reth test-vectors` subcommands +enum Subcommands { + /// Generates test vectors for specified tables. If no table is specified, generate for all. + Tables { + /// List of table names. Case-sensitive. + names: Vec<String>, + }, + /// Generates test vectors for `Compact` types with `--write`. Reads and checks generated + /// vectors with `--read`. + #[group(multiple = false, required = true)] + Compact { + /// Write test vectors to a file. + #[arg(long)] + write: bool, + + /// Read test vectors from a file. + #[arg(long)] + read: bool, + }, +} + +impl Command { + /// Execute the command + pub async fn execute(self) -> eyre::Result<()> { + match self.command { + Subcommands::Tables { names } => { + tables::generate_vectors(names)?; + } + Subcommands::Compact { write, .. } => { + compact_types!( + regular: [ + TxL1Message + ], identifier: [] + ); + + if write { + compact::generate_vectors_with(ETH_GENERATE_VECTORS)?; + compact::generate_vectors_with(GENERATE_VECTORS)?; + } else { + compact::read_vectors_with(ETH_READ_VECTORS)?; + compact::read_vectors_with(READ_VECTORS)?; + } + } + } + Ok(()) + } +}
diff --git reth/crates/scroll/cli/src/lib.rs scroll-reth/crates/scroll/cli/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..e81f6f074819b035318b59d957f83ccd777234a2 --- /dev/null +++ scroll-reth/crates/scroll/cli/src/lib.rs @@ -0,0 +1,124 @@ +//! Scroll CLI implementation. + +mod app; +pub use app::CliApp; + +mod commands; +pub use commands::Commands; + +mod spec; +pub use spec::ScrollChainSpecParser; + +use clap::{value_parser, Parser}; +use reth_cli::chainspec::ChainSpecParser; +use reth_cli_commands::{launcher::FnLauncher, node::NoArgs}; +use reth_cli_runner::CliRunner; +use reth_db::DatabaseEnv; +use reth_node_builder::{NodeBuilder, WithLaunchContext}; +use reth_node_core::{ + args::LogArgs, + version::{LONG_VERSION, SHORT_VERSION}, +}; +use reth_scroll_chainspec::ScrollChainSpec; +use std::{ffi::OsString, fmt, future::Future, sync::Arc}; + +/// The main scroll cli interface. +/// +/// This is the entrypoint to the executable. +#[derive(Debug, Parser)] +#[command(author, version = SHORT_VERSION, long_version = LONG_VERSION, about = "Scroll Reth", long_about = None +)] +pub struct Cli<Spec: ChainSpecParser = ScrollChainSpecParser, Ext: clap::Args + fmt::Debug = NoArgs> +{ + /// The command to run + #[command(subcommand)] + command: Commands<Spec, Ext>, + + /// The chain this node is running. + /// + /// Possible values are either a built-in chain or the path to a chain specification file. + #[arg( + long, + value_name = "CHAIN_OR_PATH", + long_help = Spec::help_message(), + default_value = Spec::SUPPORTED_CHAINS[0], + value_parser = Spec::parser(), + global = true, + )] + chain: Arc<Spec::ChainSpec>, + + /// Add a new instance of a node. + /// + /// Configures the ports of the node to avoid conflicts with the defaults. + /// This is useful for running multiple nodes on the same machine. + /// + /// Max number of instances is 200. It is chosen in a way so that it's not possible to have + /// port numbers that conflict with each other. + /// + /// Changes to the following port numbers: + /// - `DISCOVERY_PORT`: default + `instance` - 1 + /// - `AUTH_PORT`: default + `instance` * 100 - 100 + /// - `HTTP_RPC_PORT`: default - `instance` + 1 + /// - `WS_RPC_PORT`: default + `instance` * 2 - 2 + #[arg(long, value_name = "INSTANCE", global = true, default_value_t = 1, value_parser = value_parser!(u16).range(..=200) + )] + instance: u16, + + #[command(flatten)] + logs: LogArgs, +} + +impl Cli { + /// Parsers only the default CLI arguments + pub fn parse_args() -> Self { + Self::parse() + } + + /// Parsers only the default CLI arguments from the given iterator + pub fn try_parse_args_from<I, T>(itr: I) -> Result<Self, clap::error::Error> + where + I: IntoIterator<Item = T>, + T: Into<OsString> + Clone, + { + Self::try_parse_from(itr) + } +} + +impl<C, Ext> Cli<C, Ext> +where + C: ChainSpecParser<ChainSpec = ScrollChainSpec>, + Ext: clap::Args + fmt::Debug, +{ + /// Configures the CLI and returns a [`CliApp`] instance. + /// + /// This method is used to prepare the CLI for execution by wrapping it in a + /// [`CliApp`] that can be further configured before running. + pub fn configure(self) -> CliApp<C, Ext> { + CliApp::new(self) + } + + /// Execute the configured cli command. + /// + /// This accepts a closure that is used to launch the node via the + /// [`NodeCommand`](reth_cli_commands::node::NodeCommand). + pub fn run<L, Fut>(self, launcher: L) -> eyre::Result<()> + where + L: FnOnce(WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>, C::ChainSpec>>, Ext) -> Fut, + Fut: Future<Output = eyre::Result<()>>, + { + self.with_runner(CliRunner::try_default_runtime()?, launcher) + } + + /// Execute the configured cli command with the provided [`CliRunner`]. + pub fn with_runner<L, Fut>(self, runner: CliRunner, launcher: L) -> eyre::Result<()> + where + L: FnOnce(WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>, C::ChainSpec>>, Ext) -> Fut, + Fut: Future<Output = eyre::Result<()>>, + { + let mut this = self.configure(); + this.set_runner(runner); + this.run(FnLauncher::new::<C, Ext>(async move |builder, chain_spec| { + launcher(builder, chain_spec).await + })) + } +}
diff --git reth/crates/scroll/cli/src/spec.rs scroll-reth/crates/scroll/cli/src/spec.rs new file mode 100644 index 0000000000000000000000000000000000000000..d7d8a22b43232330bcdf2b042b131684a9c9e38d --- /dev/null +++ scroll-reth/crates/scroll/cli/src/spec.rs @@ -0,0 +1,21 @@ +use reth_cli::chainspec::{parse_genesis, ChainSpecParser}; +use reth_scroll_chainspec::{ScrollChainSpec, SCROLL_DEV, SCROLL_MAINNET, SCROLL_SEPOLIA}; +use std::sync::Arc; + +/// The parser for the Scroll chain specification. +#[derive(Debug, Clone)] +pub struct ScrollChainSpecParser; + +impl ChainSpecParser for ScrollChainSpecParser { + type ChainSpec = ScrollChainSpec; + const SUPPORTED_CHAINS: &'static [&'static str] = &["dev", "scroll-mainnet", "scroll-sepolia"]; + + fn parse(s: &str) -> eyre::Result<Arc<Self::ChainSpec>> { + Ok(match s { + "dev" => SCROLL_DEV.clone(), + "scroll-mainnet" => SCROLL_MAINNET.clone(), + "scroll-sepolia" => SCROLL_SEPOLIA.clone(), + _ => Arc::new(ScrollChainSpec::from_custom_genesis(parse_genesis(s)?)), + }) + } +}
diff --git reth/crates/scroll/consensus/Cargo.toml scroll-reth/crates/scroll/consensus/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..0456665bafab2a9bd3ba95d5bcdb4a2d31d79a5c --- /dev/null +++ scroll-reth/crates/scroll/consensus/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "reth-scroll-consensus" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# alloy +alloy-consensus.workspace = true +alloy-primitives = { workspace = true, features = ["getrandom"] } + +# reth +reth-chainspec.workspace = true +reth-consensus.workspace = true +reth-consensus-common.workspace = true +reth-ethereum-consensus.workspace = true +reth-execution-types.workspace = true +reth-primitives-traits.workspace = true + +# scroll +reth-scroll-primitives = { workspace = true, default-features = false } +scroll-alloy-consensus.workspace = true +scroll-alloy-hardforks.workspace = true + +# misc +thiserror.workspace = true +tracing.workspace = true + +[package.metadata.cargo-udeps.ignore] +normal = ["reth-primitives"] + +[dev-dependencies] +reth-scroll-chainspec.workspace = true
diff --git reth/crates/scroll/consensus/src/constants.rs scroll-reth/crates/scroll/consensus/src/constants.rs new file mode 100644 index 0000000000000000000000000000000000000000..188aeb896d5c9277f1236d62c3f25b3f73a59e9e --- /dev/null +++ scroll-reth/crates/scroll/consensus/src/constants.rs @@ -0,0 +1,13 @@ +use alloy_primitives::U256; + +/// The maximum value Rollup fee. +pub const MAX_ROLLUP_FEE: U256 = U256::from_limbs([u64::MAX, 0, 0, 0]); + +/// The block difficulty for in turn signing in the Clique consensus. +pub const CLIQUE_IN_TURN_DIFFICULTY: U256 = U256::from_limbs([2, 0, 0, 0]); + +/// The block difficulty for out of turn signing in the Clique consensus. +pub const CLIQUE_NO_TURN_DIFFICULTY: U256 = U256::from_limbs([1, 0, 0, 0]); + +/// Maximum allowed base fee. We would only go above this if L1 base fee hits 2931 Gwei. +pub const SCROLL_MAXIMUM_BASE_FEE: u64 = 10000000000;
diff --git reth/crates/scroll/consensus/src/error.rs scroll-reth/crates/scroll/consensus/src/error.rs new file mode 100644 index 0000000000000000000000000000000000000000..09eca6d9b715438b9aefc64569c9fad2d63b1ee2 --- /dev/null +++ scroll-reth/crates/scroll/consensus/src/error.rs @@ -0,0 +1,63 @@ +use crate::constants::SCROLL_MAXIMUM_BASE_FEE; + +use alloy_primitives::{Address, B256, B64, U256}; +use reth_consensus::ConsensusError; + +/// Scroll consensus error. +#[derive(Debug, Clone, thiserror::Error)] +pub enum ScrollConsensusError { + /// L1 [`ConsensusError`], that also occurs on L2. + #[error(transparent)] + Eth(#[from] ConsensusError), + /// Invalid L1 messages order. + #[error("invalid L1 message order")] + InvalidL1MessageOrder, + /// Block has non zero coinbase. + #[error("block coinbase not zero: {0}")] + CoinbaseNotZero(Address), + /// Block has non zero nonce. + #[error("block nonce not zero: {0:?}")] + NonceNotZero(Option<B64>), + /// Block has invalid clique nonce. + #[error("block nonce should be 0x0 or 0xffffffffffffffff: {0:?}")] + InvalidCliqueNonce(Option<B64>), + /// Block has non zero mix hash. + #[error("block mix hash not zero: {0:?}")] + MixHashNotZero(Option<B256>), + /// Block difficulty is not one. + #[error("block difficulty not one: {0}")] + DifficultyNotOne(U256), + /// Block has invalid clique difficulty. + #[error("block difficulty should be 1 or 2: {0}")] + InvalidCliqueDifficulty(U256), + /// Block extra data missing vanity. + #[error("block extra data missing vanity")] + MissingVanity, + /// Block extra data missing signature. + #[error("block extra data missing signature")] + MissingSignature, + /// Block extra data with invalid checkpoint signers. + #[error("block extra data contains invalid checkpoint signers")] + InvalidCheckpointSigners, + /// Block base fee present before Curie. + #[error("block base fee is set before Curie fork activation")] + UnexpectedBaseFee, + /// Block base fee over limit. + #[error("block base fee is over limit of {SCROLL_MAXIMUM_BASE_FEE}")] + BaseFeeOverLimit, + /// Block body has non-empty withdrawals list. + #[error("non-empty block body withdrawals list")] + WithdrawalsNonEmpty, + /// Chain spec yielded unexpected blob params. + #[error("unexpected blob params at timestamp")] + UnexpectedBlobParams, +} + +impl From<ScrollConsensusError> for ConsensusError { + fn from(value: ScrollConsensusError) -> Self { + match value { + ScrollConsensusError::Eth(eth) => eth, + err => Self::Other(err.to_string()), + } + } +}
diff --git reth/crates/scroll/consensus/src/lib.rs scroll-reth/crates/scroll/consensus/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..4e29d6c461a8e40840ee5ff0c5fa39462eedb4cc --- /dev/null +++ scroll-reth/crates/scroll/consensus/src/lib.rs @@ -0,0 +1,14 @@ +//! Scroll consensus implementation. + +extern crate alloc; + +mod constants; +pub use constants::{ + CLIQUE_IN_TURN_DIFFICULTY, CLIQUE_NO_TURN_DIFFICULTY, MAX_ROLLUP_FEE, SCROLL_MAXIMUM_BASE_FEE, +}; + +mod error; +pub use error::ScrollConsensusError; + +mod validation; +pub use validation::ScrollBeaconConsensus;
diff --git reth/crates/scroll/consensus/src/validation.rs scroll-reth/crates/scroll/consensus/src/validation.rs new file mode 100644 index 0000000000000000000000000000000000000000..552b9b63e81d35e08aa2f9317ee9cf11d368dc41 --- /dev/null +++ scroll-reth/crates/scroll/consensus/src/validation.rs @@ -0,0 +1,841 @@ +use crate::{ + constants::SCROLL_MAXIMUM_BASE_FEE, error::ScrollConsensusError, CLIQUE_IN_TURN_DIFFICULTY, + CLIQUE_NO_TURN_DIFFICULTY, +}; +use alloc::sync::Arc; + +use alloy_consensus::{BlockHeader as _, TxReceipt, EMPTY_OMMER_ROOT_HASH}; +use alloy_primitives::{b64, Address, B256, B64, U256}; +use core::fmt::Debug; +use reth_chainspec::{EthChainSpec, EthereumHardforks}; +use reth_consensus::{ + validate_state_root, Consensus, ConsensusError, FullConsensus, HeaderValidator, +}; +use reth_consensus_common::validation::{ + validate_against_parent_hash_number, validate_body_against_header, validate_header_gas, +}; +use reth_execution_types::BlockExecutionResult; +use reth_primitives_traits::{ + constants::{GAS_LIMIT_BOUND_DIVISOR, MINIMUM_GAS_LIMIT}, + receipt::gas_spent_by_transactions, + Block, BlockBody, BlockHeader, GotExpected, NodePrimitives, RecoveredBlock, SealedBlock, + SealedHeader, SignedTransaction, +}; +use reth_scroll_primitives::ScrollReceipt; +use scroll_alloy_consensus::ScrollTransaction; +use scroll_alloy_hardforks::ScrollHardforks; + +/// Scroll consensus implementation. +/// +/// Provides basic checks as outlined in the execution specs. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ScrollBeaconConsensus<ChainSpec> { + /// Configuration + chain_spec: Arc<ChainSpec>, +} + +impl<ChainSpec> ScrollBeaconConsensus<ChainSpec> { + /// Create a new instance of [`ScrollBeaconConsensus`] + pub const fn new(chain_spec: Arc<ChainSpec>) -> Self { + Self { chain_spec } + } +} + +impl< + ChainSpec: EthChainSpec + ScrollHardforks, + N: NodePrimitives<Receipt = ScrollReceipt, SignedTx: ScrollTransaction>, + > FullConsensus<N> for ScrollBeaconConsensus<ChainSpec> +{ + fn validate_block_post_execution( + &self, + block: &RecoveredBlock<N::Block>, + result: &BlockExecutionResult<N::Receipt>, + ) -> Result<(), ConsensusError> { + // verify the block gas used + let cumulative_gas_used = + result.receipts.last().map(|r| r.cumulative_gas_used()).unwrap_or(0); + if block.gas_used() != cumulative_gas_used { + return Err(ConsensusError::BlockGasUsed { + gas: GotExpected { got: cumulative_gas_used, expected: block.gas_used() }, + gas_spent_by_tx: gas_spent_by_transactions(&result.receipts), + }); + } + + // verify the receipts logs bloom and root + if self.chain_spec.is_byzantium_active_at_block(block.header().number()) { + if let Err(error) = reth_ethereum_consensus::verify_receipts( + block.header().receipts_root(), + block.header().logs_bloom(), + &result.receipts, + ) { + tracing::debug!( + %error, + ?result.receipts, + header_receipt_root = ?block.header().receipts_root(), + header_bloom = ?block.header().logs_bloom(), + "failed to verify receipts" + ); + return Err(error); + } + } + + Ok(()) + } +} + +/// Following fields should be checked on body: +/// - Verify no ommers are present and hash to the header ommer root. +/// - Verify transactions trie root is valid. +/// - Validate L1 messages: should be at the start of the list of transactions and been continuous +/// in regard to the queue index. +impl<ChainSpec, B> Consensus<B> for ScrollBeaconConsensus<ChainSpec> +where + B: Block, + <B::Body as BlockBody>::Transaction: ScrollTransaction, + ChainSpec: EthChainSpec + ScrollHardforks, +{ + type Error = ConsensusError; + + fn validate_body_against_header( + &self, + body: &B::Body, + header: &SealedHeader<B::Header>, + ) -> Result<(), ConsensusError> { + validate_body_against_header(body, header.header()) + } + + fn validate_block_pre_execution(&self, block: &SealedBlock<B>) -> Result<(), ConsensusError> { + // Check no ommers. + let ommers_len = block.body().ommers().map(|o| o.len()).unwrap_or_default(); + if ommers_len > 0 { + return Err(ConsensusError::Other("uncles not allowed".to_string())) + } + + // Check ommers hash + let ommers_hash = block.body().calculate_ommers_root(); + if Some(block.ommers_hash()) != ommers_hash { + return Err(ConsensusError::BodyOmmersHashDiff( + GotExpected { + got: ommers_hash.unwrap_or(EMPTY_OMMER_ROOT_HASH), + expected: block.ommers_hash(), + } + .into(), + )) + } + + // Check transaction root + if let Err(error) = block.ensure_transaction_root_valid() { + return Err(ConsensusError::BodyTransactionRootDiff(error.into())) + } + + // Check withdrawals are empty + if block.body().withdrawals().is_some() { + return Err(ConsensusError::Other(ScrollConsensusError::WithdrawalsNonEmpty.to_string())) + } + + // Check L1 messages. + let ts = block.header().timestamp(); + validate_l1_messages( + block.body().transactions(), + self.chain_spec.is_euclid_v2_active_at_timestamp(ts), + )?; + + Ok(()) + } +} + +impl<ChainSpec: EthChainSpec + ScrollHardforks, H: BlockHeader> HeaderValidator<H> + for ScrollBeaconConsensus<ChainSpec> +{ + fn validate_header(&self, header: &SealedHeader<H>) -> Result<(), ConsensusError> { + validate_header_timestamp(header.header())?; + validate_header_fields(header.header(), &self.chain_spec)?; + validate_header_gas(header.header())?; + validate_header_base_fee(header.header(), &self.chain_spec)?; + Ok(()) + } + + fn validate_header_against_parent( + &self, + header: &SealedHeader<H>, + parent: &SealedHeader<H>, + ) -> Result<(), ConsensusError> { + validate_against_parent_hash_number(header.header(), parent)?; + validate_against_parent_timestamp(header.header(), parent.header())?; + validate_against_parent_gas_limit(header.header(), parent.header())?; + + // ensure that the blob gas fields for this block + if self.chain_spec.blob_params_at_timestamp(header.timestamp()).is_some() { + return Err(ConsensusError::Other( + ScrollConsensusError::UnexpectedBlobParams.to_string(), + )) + } + + Ok(()) + } + + fn validate_state_root(&self, header: &H, root: B256) -> Result<(), ConsensusError> { + if self.chain_spec.is_euclid_active_at_timestamp(header.timestamp()) { + validate_state_root(header, root)?; + } + + Ok(()) + } +} + +#[inline] +fn validate_header_fields<H: BlockHeader, ChainSpec: EthChainSpec + ScrollHardforks>( + header: &H, + chain_spec: ChainSpec, +) -> Result<(), ScrollConsensusError> { + // Common checks to pre and post Euclid v2. + if header.ommers_hash() != EMPTY_OMMER_ROOT_HASH { + return Err(ConsensusError::TheMergeOmmerRootIsNotEmpty.into()) + } + if header.mix_hash() != Some(B256::ZERO) { + return Err(ScrollConsensusError::MixHashNotZero(header.mix_hash())) + } + + if chain_spec.is_euclid_v2_active_at_timestamp(header.timestamp()) { + verify_header_fields_post_euclid_v2(header)?; + } else { + let clique_config = + chain_spec.genesis().config.clique.expect("clique config required pre euclid v2"); + let epoch = clique_config.epoch.expect("epoch required pre euclid v2"); + verify_header_fields_pre_euclid_v2(header, epoch)?; + } + + Ok(()) +} + +/// Verify the header's field for post Euclid v2 blocks. +#[inline] +fn verify_header_fields_post_euclid_v2<H: BlockHeader>( + header: &H, +) -> Result<(), ScrollConsensusError> { + if header.beneficiary() != Address::ZERO { + return Err(ScrollConsensusError::CoinbaseNotZero(header.beneficiary())) + } + if header.nonce() != Some(B64::ZERO) { + return Err(ScrollConsensusError::NonceNotZero(header.nonce())) + } + if header.difficulty() != U256::ONE { + return Err(ScrollConsensusError::DifficultyNotOne(header.difficulty())) + } + if !header.extra_data().is_empty() { + return Err(ConsensusError::ExtraDataExceedsMax { len: header.extra_data().len() }.into()) + } + + Ok(()) +} + +/// Verify the header's field for pre Euclid v2 blocks. +#[inline] +fn verify_header_fields_pre_euclid_v2<H: BlockHeader>( + header: &H, + epoch: u64, +) -> Result<(), ScrollConsensusError> { + let is_checkpoint = header.number().is_multiple_of(epoch); + if is_checkpoint && header.beneficiary() != Address::ZERO { + return Err(ScrollConsensusError::CoinbaseNotZero(header.beneficiary())) + } + if header.nonce() != Some(B64::ZERO) && header.nonce() != Some(b64!("ffffffffffffffff")) { + return Err(ScrollConsensusError::InvalidCliqueNonce(header.nonce())) + } + if is_checkpoint && header.nonce() != Some(B64::ZERO) { + return Err(ScrollConsensusError::NonceNotZero(header.nonce())) + } + if header.extra_data().len() < 32 { + return Err(ScrollConsensusError::MissingVanity) + } + if header.extra_data().len() < 32 + 65 { + return Err(ScrollConsensusError::MissingSignature) + } + let signer_bytes = header.extra_data().len() - 32 - 65; + if !is_checkpoint && signer_bytes > 0 { + return Err(ScrollConsensusError::InvalidCheckpointSigners) + } + let difficulty = header.difficulty(); + if difficulty != CLIQUE_IN_TURN_DIFFICULTY && difficulty != CLIQUE_NO_TURN_DIFFICULTY { + return Err(ScrollConsensusError::InvalidCliqueDifficulty(difficulty)) + } + + Ok(()) +} + +/// Validates the timestamp of the header, which should not be in the future. +#[inline] +fn validate_header_timestamp<H: BlockHeader>(header: &H) -> Result<(), ConsensusError> { + let now = std::time::SystemTime::now(); + let since_unix_epoch = now.duration_since(std::time::SystemTime::UNIX_EPOCH).unwrap().as_secs(); + if header.timestamp() > since_unix_epoch { + return Err(ConsensusError::TimestampIsInPast { + parent_timestamp: since_unix_epoch, + timestamp: header.timestamp(), + }) + } + Ok(()) +} + +/// Ensure the EIP-1559 base fee is set if the Curie hardfork is active. +#[inline] +fn validate_header_base_fee<H: BlockHeader, ChainSpec: ScrollHardforks>( + header: &H, + chain_spec: &ChainSpec, +) -> Result<(), ScrollConsensusError> { + if chain_spec.is_curie_active_at_block(header.number()) { + if header.base_fee_per_gas().is_none() { + return Err(ConsensusError::BaseFeeMissing.into()) + } + // note: we do not verify L2 base fee, the sequencer has the + // right to set any base fee below the maximum. L2 base fee + // is not subject to L2 consensus or zk verification. + if header.base_fee_per_gas().expect("checked") > SCROLL_MAXIMUM_BASE_FEE { + return Err(ScrollConsensusError::BaseFeeOverLimit) + } + } + if !chain_spec.is_curie_active_at_block(header.number()) && header.base_fee_per_gas().is_some() + { + return Err(ScrollConsensusError::UnexpectedBaseFee) + } + Ok(()) +} + +/// Validates the timestamp against the parent to make sure it is in the past. +/// In Scroll, we can have parent.timestamp == header.timestamp which is why +/// we modify this validation compared to +/// [`reth_consensus_common::validation::validate_against_parent_timestamp`]. +#[inline] +fn validate_against_parent_timestamp<H: BlockHeader>( + header: &H, + parent: &H, +) -> Result<(), ConsensusError> { + if header.timestamp() < parent.timestamp() { + return Err(ConsensusError::TimestampIsInPast { + parent_timestamp: parent.timestamp(), + timestamp: header.timestamp(), + }) + } + Ok(()) +} + +/// Validates the gas limit of the block against the parent. +#[inline] +fn validate_against_parent_gas_limit<H: BlockHeader>( + header: &H, + parent: &H, +) -> Result<(), ConsensusError> { + let diff = header.gas_limit().abs_diff(parent.gas_limit()); + let limit = parent.gas_limit() / GAS_LIMIT_BOUND_DIVISOR; + if diff > limit { + return if header.gas_limit() > parent.gas_limit() { + Err(ConsensusError::GasLimitInvalidIncrease { + parent_gas_limit: parent.gas_limit(), + child_gas_limit: parent.gas_limit(), + }) + } else { + Err(ConsensusError::GasLimitInvalidDecrease { + parent_gas_limit: parent.gas_limit(), + child_gas_limit: parent.gas_limit(), + }) + } + } + // Check that the gas limit is above the minimum allowed gas limit. + if header.gas_limit() < MINIMUM_GAS_LIMIT { + return Err(ConsensusError::GasLimitInvalidMinimum { child_gas_limit: header.gas_limit() }) + } + Ok(()) +} + +/// Validate the L1 messages by checking they are only present that the start of the block and only +/// have increasing queue index. +#[inline] +fn validate_l1_messages<Tx: SignedTransaction + ScrollTransaction>( + txs: &[Tx], + is_euclid_v2: bool, +) -> Result<(), ScrollConsensusError> { + // Check L1 messages are only at the start of the block and correctly ordered. + let mut saw_l2_transaction = false; + let mut queue_index = txs + .iter() + .find(|tx| tx.is_l1_message()) + .and_then(|tx| tx.queue_index()) + .unwrap_or_default(); + + // starting at EuclidV2, we don't skip L1 messages. + let l1_message_index_check: fn(u64, u64) -> bool = if is_euclid_v2 { + |tx_queue_index, queue_index| tx_queue_index != queue_index + } else { + |tx_queue_index, queue_index| tx_queue_index < queue_index + }; + + for tx in txs { + // Check index is strictly increasing pre EuclidV2 and sequential post EuclidV2. + if tx.is_l1_message() { + let tx_queue_index = tx.queue_index().expect("is_l1_message"); + if l1_message_index_check(tx_queue_index, queue_index) { + return Err(ScrollConsensusError::InvalidL1MessageOrder); + } + queue_index = tx_queue_index + 1; + } + + // Check correct ordering. + if tx.is_l1_message() && saw_l2_transaction { + return Err(ScrollConsensusError::InvalidL1MessageOrder); + } + saw_l2_transaction = !tx.is_l1_message(); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ScrollConsensusError; + + use alloy_consensus::{Header, Signed, TxEip1559}; + use alloy_primitives::{b64, Address, Bloom, Bytes, Signature, B256, U256}; + use reth_consensus::ConsensusError; + use reth_primitives_traits::constants::{GAS_LIMIT_BOUND_DIVISOR, MINIMUM_GAS_LIMIT}; + use reth_scroll_chainspec::SCROLL_MAINNET; + use scroll_alloy_consensus::{ScrollTxEnvelope, TxL1Message}; + + fn create_test_header() -> Header { + Header { + parent_hash: B256::random(), + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: Address::ZERO, + state_root: B256::random(), + transactions_root: B256::random(), + receipts_root: B256::random(), + logs_bloom: Bloom::default(), + difficulty: U256::ONE, + number: 1, + gas_limit: 30000000, + gas_used: 0, + timestamp: 1000, + extra_data: Bytes::new(), + mix_hash: B256::ZERO, + nonce: B64::ZERO, + base_fee_per_gas: None, + withdrawals_root: None, + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, + requests_hash: None, + } + } + + #[test] + fn test_validate_header_timestamp_success() { + let header = create_test_header(); + assert!(validate_header_timestamp(&header).is_ok()); + } + + #[test] + fn test_validate_header_timestamp_future() { + let mut header = create_test_header(); + // timestamp in the future. + header.timestamp = u64::MAX; + + let result = validate_header_timestamp(&header); + assert!(matches!(result, Err(ConsensusError::TimestampIsInPast { .. }))); + } + + #[test] + fn test_verify_header_fields_post_euclid_v2_success() { + let header = create_test_header(); + assert!(verify_header_fields_post_euclid_v2(&header).is_ok()); + } + + #[test] + fn test_verify_header_fields_post_euclid_v2_coinbase_not_zero() { + let mut header = create_test_header(); + header.beneficiary = Address::random(); + + let result = verify_header_fields_post_euclid_v2(&header); + assert!(matches!(result, Err(ScrollConsensusError::CoinbaseNotZero(_)))); + } + + #[test] + fn test_verify_header_fields_post_euclid_v2_nonce_not_zero() { + let mut header = create_test_header(); + header.nonce = b64!("0123456789abcdef"); + + let result = verify_header_fields_post_euclid_v2(&header); + assert!(matches!(result, Err(ScrollConsensusError::NonceNotZero(_)))); + } + + #[test] + fn test_verify_header_fields_post_euclid_v2_difficulty_not_one() { + let mut header = create_test_header(); + header.difficulty = U256::from(2); + + let result = verify_header_fields_post_euclid_v2(&header); + assert!(matches!(result, Err(ScrollConsensusError::DifficultyNotOne(_)))); + } + + #[test] + fn test_verify_header_fields_post_euclid_v2_extra_data_not_empty() { + let mut header = create_test_header(); + header.extra_data = Bytes::from(vec![1, 2, 3]); + + let result = verify_header_fields_post_euclid_v2(&header); + assert!(matches!( + result, + Err(ScrollConsensusError::Eth(ConsensusError::ExtraDataExceedsMax { .. })) + )); + } + + #[test] + fn test_verify_header_fields_pre_euclid_v2_success() { + let mut header = create_test_header(); + // valid extra data (32 bytes vanity + 65 bytes signature). + let mut extra_data = vec![0u8; 32]; + extra_data.extend_from_slice(&[0u8; 65]); + header.extra_data = Bytes::from(extra_data); + + assert!(verify_header_fields_pre_euclid_v2(&header, 30000).is_ok()); + } + + #[test] + fn test_verify_header_fields_pre_euclid_v2_checkpoint_coinbase_not_zero() { + let mut header = create_test_header(); + // checkpoint block. + header.number = 0; + header.beneficiary = Address::random(); + let mut extra_data = vec![0u8; 32]; + extra_data.extend_from_slice(&[0u8; 65]); + header.extra_data = Bytes::from(extra_data); + + let result = verify_header_fields_pre_euclid_v2(&header, 30000); + assert!(matches!(result, Err(ScrollConsensusError::CoinbaseNotZero(_)))); + } + + #[test] + fn test_verify_header_fields_pre_euclid_v2_invalid_nonce() { + let mut header = create_test_header(); + // invalid nonce. + header.nonce = b64!("1234567890abcdef"); + let mut extra_data = vec![0u8; 32]; + extra_data.extend_from_slice(&[0u8; 65]); + header.extra_data = Bytes::from(extra_data); + + let result = verify_header_fields_pre_euclid_v2(&header, 30000); + assert!(matches!(result, Err(ScrollConsensusError::InvalidCliqueNonce(_)))); + } + + #[test] + fn test_verify_header_fields_pre_euclid_v2_missing_vanity() { + let mut header = create_test_header(); + // vanity too short. + header.extra_data = Bytes::from(vec![0u8; 31]); + + let result = verify_header_fields_pre_euclid_v2(&header, 30000); + assert!(matches!(result, Err(ScrollConsensusError::MissingVanity))); + } + + #[test] + fn test_verify_header_fields_pre_euclid_v2_missing_signature() { + let mut header = create_test_header(); + // signature too short. + header.extra_data = Bytes::from(vec![0u8; 32]); + + let result = verify_header_fields_pre_euclid_v2(&header, 30000); + assert!(matches!(result, Err(ScrollConsensusError::MissingSignature))); + } + + #[test] + fn test_verify_header_fields_pre_euclid_v2_invalid_difficulty() { + let mut header = create_test_header(); + // invalid difficulty. + header.difficulty = U256::from(3); + let mut extra_data = vec![0u8; 32]; + extra_data.extend_from_slice(&[0u8; 65]); + header.extra_data = Bytes::from(extra_data); + + let result = verify_header_fields_pre_euclid_v2(&header, 30000); + assert!(matches!(result, Err(ScrollConsensusError::InvalidCliqueDifficulty(_)))); + } + + #[test] + fn test_validate_against_parent_timestamp_success() { + let parent = create_test_header(); + let mut header = create_test_header(); + header.timestamp = parent.timestamp + 1; + + assert!(validate_against_parent_timestamp(&header, &parent).is_ok()); + } + + #[test] + fn test_validate_against_parent_timestamp_same_time() { + let parent = create_test_header(); + let header = create_test_header(); + + assert!(validate_against_parent_timestamp(&header, &parent).is_ok()); + } + + #[test] + fn test_validate_against_parent_timestamp_in_past() { + let parent = create_test_header(); + let mut header = create_test_header(); + header.timestamp = parent.timestamp - 1; + + let result = validate_against_parent_timestamp(&header, &parent); + assert!(matches!(result, Err(ConsensusError::TimestampIsInPast { .. }))); + } + + #[test] + fn test_validate_against_parent_gas_limit_success() { + let parent = create_test_header(); + let mut header = create_test_header(); + // small gas increase. + header.gas_limit = parent.gas_limit + 100; + + assert!(validate_against_parent_gas_limit(&header, &parent).is_ok()); + } + + #[test] + fn test_validate_against_parent_gas_limit_too_high_increase() { + let parent = create_test_header(); + let mut header = create_test_header(); + header.gas_limit = parent.gas_limit + parent.gas_limit / GAS_LIMIT_BOUND_DIVISOR + 1; + + let result = validate_against_parent_gas_limit(&header, &parent); + assert!(matches!(result, Err(ConsensusError::GasLimitInvalidIncrease { .. }))); + } + + #[test] + fn test_validate_against_parent_gas_limit_too_high_decrease() { + let parent = create_test_header(); + let mut header = create_test_header(); + header.gas_limit = parent.gas_limit - parent.gas_limit / GAS_LIMIT_BOUND_DIVISOR - 1; + + let result = validate_against_parent_gas_limit(&header, &parent); + assert!(matches!(result, Err(ConsensusError::GasLimitInvalidDecrease { .. }))); + } + + #[test] + fn test_validate_against_parent_gas_limit_below_minimum() { + let mut parent = create_test_header(); + let mut header = create_test_header(); + parent.gas_limit = MINIMUM_GAS_LIMIT + 1; + header.gas_limit = MINIMUM_GAS_LIMIT - 1; + + let result = validate_against_parent_gas_limit(&header, &parent); + dbg!(&result); + assert!(matches!(result, Err(ConsensusError::GasLimitInvalidMinimum { .. }))); + } + + #[test] + fn test_validate_l1_messages_success() { + let txs: Vec<ScrollTxEnvelope> = vec![ + TxL1Message { queue_index: 0, ..Default::default() }.into(), + TxL1Message { queue_index: 1, ..Default::default() }.into(), + Signed::new_unchecked( + TxEip1559::default(), + Signature::new(U256::ZERO, U256::ZERO, false), + B256::random(), + ) + .into(), + Signed::new_unchecked( + TxEip1559::default(), + Signature::new(U256::ZERO, U256::ZERO, false), + B256::random(), + ) + .into(), + ]; + + assert!(validate_l1_messages(&txs, true).is_ok()); + assert!(validate_l1_messages(&txs, false).is_ok()); + } + + #[test] + fn test_validate_l1_messages_empty() { + let txs: Vec<ScrollTxEnvelope> = vec![]; + assert!(validate_l1_messages(&txs, true).is_ok()); + assert!(validate_l1_messages(&txs, false).is_ok()); + } + + #[test] + fn test_validate_l1_messages_only_l2() { + let txs: Vec<ScrollTxEnvelope> = vec![ + Signed::new_unchecked( + TxEip1559::default(), + Signature::new(U256::ZERO, U256::ZERO, false), + B256::random(), + ) + .into(), + Signed::new_unchecked( + TxEip1559::default(), + Signature::new(U256::ZERO, U256::ZERO, false), + B256::random(), + ) + .into(), + Signed::new_unchecked( + TxEip1559::default(), + Signature::new(U256::ZERO, U256::ZERO, false), + B256::random(), + ) + .into(), + Signed::new_unchecked( + TxEip1559::default(), + Signature::new(U256::ZERO, U256::ZERO, false), + B256::random(), + ) + .into(), + ]; + + assert!(validate_l1_messages(&txs, true).is_ok()); + assert!(validate_l1_messages(&txs, false).is_ok()); + } + + #[test] + fn test_validate_l1_messages_invalid_order() { + let txs: Vec<ScrollTxEnvelope> = vec![ + Signed::new_unchecked( + TxEip1559::default(), + Signature::new(U256::ZERO, U256::ZERO, false), + B256::random(), + ) + .into(), + TxL1Message { queue_index: 0, ..Default::default() }.into(), + ]; + + let result = validate_l1_messages(&txs, true); + assert!(matches!(result, Err(ScrollConsensusError::InvalidL1MessageOrder))); + let result = validate_l1_messages(&txs, false); + assert!(matches!(result, Err(ScrollConsensusError::InvalidL1MessageOrder))); + } + + #[test] + fn test_validate_l1_messages_non_sequential_queue_index() { + let txs: Vec<ScrollTxEnvelope> = vec![ + TxL1Message { queue_index: 0, ..Default::default() }.into(), + TxL1Message { queue_index: 2, ..Default::default() }.into(), + ]; + + // ok as it's not decreasing. + assert!(validate_l1_messages(&txs, false).is_ok()); + // not ok as it's not sequential. + let result = validate_l1_messages(&txs, true); + assert!(matches!(result, Err(ScrollConsensusError::InvalidL1MessageOrder))); + } + + #[test] + fn test_validate_l1_messages_decreasing_queue_index() { + let txs: Vec<ScrollTxEnvelope> = vec![ + TxL1Message { queue_index: 1, ..Default::default() }.into(), + TxL1Message { queue_index: 0, ..Default::default() }.into(), + ]; + + let result = validate_l1_messages(&txs, true); + assert!(matches!(result, Err(ScrollConsensusError::InvalidL1MessageOrder))); + let result = validate_l1_messages(&txs, false); + assert!(matches!(result, Err(ScrollConsensusError::InvalidL1MessageOrder))); + } + + #[test] + fn test_validate_header_base_fee_before_curie() { + let chain_spec = SCROLL_MAINNET.clone(); + + let mut header = create_test_header(); + // pre Curie. + header.number = 500; + header.base_fee_per_gas = Some(1000000000); + + let result = validate_header_base_fee(&header, &chain_spec); + assert!(matches!(result, Err(ScrollConsensusError::UnexpectedBaseFee))); + } + + #[test] + fn test_validate_header_base_fee_after_curie_missing() { + let chain_spec = SCROLL_MAINNET.clone(); + + let mut header = create_test_header(); + // post Curie. + header.number = 7096837; + header.base_fee_per_gas = None; + + let result = validate_header_base_fee(&header, &chain_spec); + assert!(matches!(result, Err(ScrollConsensusError::Eth(ConsensusError::BaseFeeMissing)))); + } + + #[test] + fn test_validate_header_base_fee_after_curie_over_limit() { + let chain_spec = SCROLL_MAINNET.clone(); + + let mut header = create_test_header(); + // post Curie. + header.number = 7096837; + header.base_fee_per_gas = Some(SCROLL_MAXIMUM_BASE_FEE + 1); + + let result = validate_header_base_fee(&header, &chain_spec); + assert!(matches!(result, Err(ScrollConsensusError::BaseFeeOverLimit))); + } + + #[test] + fn test_validate_header_base_fee_after_curie_valid() { + let chain_spec = SCROLL_MAINNET.clone(); + + let mut header = create_test_header(); + // post Curie. + header.number = 7096837; + header.base_fee_per_gas = Some(1000000000); + + let result = validate_header_base_fee(&header, &chain_spec); + assert!(result.is_ok()); + } + + #[test] + fn test_validate_header_fields_pre_euclid_v2() { + let mut header = create_test_header(); + // pre Euclid v2. + header.timestamp = 1745305199; + // valid extra data for pre-euclid v2. + let mut extra_data = vec![0u8; 32]; + extra_data.extend_from_slice(&[0u8; 65]); + header.extra_data = Bytes::from(extra_data); + + assert!(verify_header_fields_pre_euclid_v2(&header, 30000).is_ok()); + } + + #[test] + fn test_validate_header_fields_post_euclid_v2() { + let chain_spec = SCROLL_MAINNET.clone(); + + let mut header = create_test_header(); + // post Euclid v2. + header.timestamp = 1745305201; + + assert!(validate_header_fields(&header, &chain_spec).is_ok()); + } + + #[test] + fn test_validate_header_fields_mix_hash_not_zero() { + let chain_spec = SCROLL_MAINNET.clone(); + + let mut header = create_test_header(); + // invalid mix hash. + header.mix_hash = B256::random(); + + let result = validate_header_fields(&header, &chain_spec); + assert!(matches!(result, Err(ScrollConsensusError::MixHashNotZero(_)))); + } + + #[test] + fn test_validate_header_fields_ommers_hash_not_empty() { + let chain_spec = SCROLL_MAINNET.clone(); + + let mut header = create_test_header(); + // invalid ommer hash. + header.ommers_hash = B256::random(); + + let result = validate_header_fields(&header, &chain_spec); + assert!(matches!( + result, + Err(ScrollConsensusError::Eth(ConsensusError::TheMergeOmmerRootIsNotEmpty)) + )); + } +}
diff --git reth/crates/scroll/engine-primitives/Cargo.toml scroll-reth/crates/scroll/engine-primitives/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..bb24a42a903ce53184ed40d4698942b1ec69b50b --- /dev/null +++ scroll-reth/crates/scroll/engine-primitives/Cargo.toml @@ -0,0 +1,65 @@ +[package] +name = "reth-scroll-engine-primitives" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# reth +reth-chain-state.workspace = true +reth-chainspec.workspace = true +reth-engine-primitives.workspace = true +reth-payload-builder.workspace = true +reth-payload-primitives = { workspace = true, features = ["scroll-alloy-traits"] } +reth-primitives = { workspace = true, features = ["serde-bincode-compat", "reth-codec"] } +reth-primitives-traits.workspace = true + +# alloy +alloy-consensus.workspace = true +alloy-eips.workspace = true +alloy-primitives.workspace = true +alloy-rlp.workspace = true +alloy-rpc-types-engine.workspace = true + +# scroll +reth-scroll-chainspec.workspace = true +reth-scroll-primitives = { workspace = true, features = ["serde", "serde-bincode-compat", "reth-codec"] } +scroll-alloy-rpc-types-engine.workspace = true +scroll-alloy-hardforks.workspace = true + +# misc +serde.workspace = true +sha2 = { workspace = true, default-features = false } + +[dev-dependencies] +alloy-primitives = { workspace = true, features = ["getrandom"] } +arbitrary.workspace = true +eyre.workspace = true +rand.workspace = true + +[features] +default = ["std"] +std = [ + "alloy-consensus/std", + "alloy-eips/std", + "alloy-primitives/std", + "alloy-rlp/std", + "alloy-rpc-types-engine/std", + "reth-chainspec/std", + "reth-engine-primitives/std", + "reth-primitives/std", + "reth-primitives-traits/std", + "serde/std", + "sha2/std", + "reth-scroll-chainspec/std", + "scroll-alloy-hardforks/std", + "scroll-alloy-rpc-types-engine/std", + "reth-scroll-primitives/std", +]
diff --git reth/crates/scroll/engine-primitives/src/lib.rs scroll-reth/crates/scroll/engine-primitives/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..a82ec2581c09f531783073d2bb2682d94082797d --- /dev/null +++ scroll-reth/crates/scroll/engine-primitives/src/lib.rs @@ -0,0 +1,13 @@ +//! The engine primitives for Scroll. + +#![cfg_attr(not(feature = "std"), no_std)] +#[cfg(not(feature = "std"))] +extern crate alloc as std; + +mod payload; +pub use payload::{ + try_into_block, ScrollBuiltPayload, ScrollEngineTypes, ScrollPayloadBuilderAttributes, + ScrollPayloadTypes, +}; + +extern crate alloc;
diff --git reth/crates/scroll/engine-primitives/src/payload/attributes.rs scroll-reth/crates/scroll/engine-primitives/src/payload/attributes.rs new file mode 100644 index 0000000000000000000000000000000000000000..1579845c09c5d71b65d284d7437d514dbf88f1f2 --- /dev/null +++ scroll-reth/crates/scroll/engine-primitives/src/payload/attributes.rs @@ -0,0 +1,219 @@ +//! Payload related types + +use alloc::vec::Vec; +use std::fmt::Debug; + +use alloy_eips::{eip2718::Decodable2718, eip4895::Withdrawals}; +use alloy_primitives::{keccak256, Address, B256}; +use alloy_rlp::Encodable; +use alloy_rpc_types_engine::PayloadId; +use reth_payload_builder::EthPayloadBuilderAttributes; +use reth_payload_primitives::PayloadBuilderAttributes; +use reth_primitives::transaction::WithEncoded; +use reth_scroll_primitives::ScrollTransactionSigned; +use scroll_alloy_rpc_types_engine::{BlockDataHint, ScrollPayloadAttributes}; + +/// Scroll Payload Builder Attributes +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct ScrollPayloadBuilderAttributes { + /// Inner ethereum payload builder attributes + pub payload_attributes: EthPayloadBuilderAttributes, + /// `NoTxPool` option for the generated payload + pub no_tx_pool: bool, + /// Decoded transactions and the original EIP-2718 encoded bytes as received in the payload + /// attributes. + pub transactions: Vec<WithEncoded<ScrollTransactionSigned>>, + /// The block data hint, used pre-Euclid by the block builder to derive the correct block + /// hash and post-Euclid by the sequencer to set the difficulty of the block. + pub block_data_hint: BlockDataHint, + /// The gas limit for the generated payload. + pub gas_limit: Option<u64>, +} + +impl PayloadBuilderAttributes for ScrollPayloadBuilderAttributes { + type RpcPayloadAttributes = ScrollPayloadAttributes; + type Error = alloy_rlp::Error; + + fn try_new( + parent: B256, + attributes: ScrollPayloadAttributes, + version: u8, + ) -> Result<Self, Self::Error> { + let id = payload_id_scroll(&parent, &attributes, version); + + let transactions = attributes + .transactions + .unwrap_or_default() + .into_iter() + .map(|data| { + let mut buf = data.as_ref(); + let tx = Decodable2718::decode_2718(&mut buf).map_err(alloy_rlp::Error::from)?; + + if !buf.is_empty() { + return Err(alloy_rlp::Error::UnexpectedLength); + } + + Ok(WithEncoded::new(data, tx)) + }) + .collect::<Result<_, _>>()?; + + let payload_attributes = EthPayloadBuilderAttributes { + id, + parent, + timestamp: attributes.payload_attributes.timestamp, + suggested_fee_recipient: attributes.payload_attributes.suggested_fee_recipient, + prev_randao: attributes.payload_attributes.prev_randao, + withdrawals: attributes.payload_attributes.withdrawals.unwrap_or_default().into(), + parent_beacon_block_root: attributes.payload_attributes.parent_beacon_block_root, + }; + + Ok(Self { + payload_attributes, + no_tx_pool: attributes.no_tx_pool, + transactions, + block_data_hint: attributes.block_data_hint, + gas_limit: attributes.gas_limit, + }) + } + + fn payload_id(&self) -> PayloadId { + self.payload_attributes.id + } + + fn parent(&self) -> B256 { + self.payload_attributes.parent + } + + fn timestamp(&self) -> u64 { + self.payload_attributes.timestamp + } + + fn parent_beacon_block_root(&self) -> Option<B256> { + self.payload_attributes.parent_beacon_block_root + } + + fn suggested_fee_recipient(&self) -> Address { + self.payload_attributes.suggested_fee_recipient + } + + fn prev_randao(&self) -> B256 { + self.payload_attributes.prev_randao + } + + fn withdrawals(&self) -> &Withdrawals { + &self.payload_attributes.withdrawals + } +} + +/// Generates the payload id for the configured payload from the [`ScrollPayloadAttributes`]. +/// +/// Returns an 8-byte identifier by hashing the payload components with sha256 hash. +pub(crate) fn payload_id_scroll( + parent: &B256, + attributes: &ScrollPayloadAttributes, + payload_version: u8, +) -> PayloadId { + use sha2::Digest; + let mut hasher = sha2::Sha256::new(); + hasher.update(parent.as_slice()); + hasher.update(&attributes.payload_attributes.timestamp.to_be_bytes()[..]); + hasher.update(attributes.payload_attributes.prev_randao.as_slice()); + hasher.update(attributes.payload_attributes.suggested_fee_recipient.as_slice()); + if let Some(withdrawals) = &attributes.payload_attributes.withdrawals { + let mut buf = Vec::new(); + withdrawals.encode(&mut buf); + hasher.update(buf); + } + + if let Some(parent_beacon_block) = attributes.payload_attributes.parent_beacon_block_root { + hasher.update(parent_beacon_block); + } + + let no_tx_pool = attributes.no_tx_pool; + if no_tx_pool || attributes.transactions.as_ref().is_some_and(|txs| !txs.is_empty()) { + hasher.update([no_tx_pool as u8]); + let txs_len = attributes.transactions.as_ref().map(|txs| txs.len()).unwrap_or_default(); + hasher.update(&txs_len.to_be_bytes()[..]); + if let Some(txs) = &attributes.transactions { + for tx in txs { + // we have to just hash the bytes here because otherwise we would need to decode + // the transactions here which really isn't ideal + let tx_hash = keccak256(tx); + // maybe we can try just taking the hash and not decoding + hasher.update(tx_hash) + } + } + } + + if let Some(extra_data) = &attributes.block_data_hint.extra_data { + hasher.update(extra_data); + } + if let Some(state_root) = &attributes.block_data_hint.state_root { + hasher.update(state_root.0); + } + if let Some(coinbase) = &attributes.block_data_hint.coinbase { + hasher.update(coinbase); + } + if let Some(nonce) = &attributes.block_data_hint.nonce { + hasher.update(nonce.to_be_bytes()); + } + if let Some(difficulty) = &attributes.block_data_hint.difficulty { + hasher.update(difficulty.to_be_bytes::<32>()); + } + if let Some(gas_limit) = attributes.gas_limit { + hasher.update(gas_limit.to_be_bytes()); + } + + let mut out = hasher.finalize(); + out[0] = payload_version; + PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length")) +} + +impl From<EthPayloadBuilderAttributes> for ScrollPayloadBuilderAttributes { + fn from(value: EthPayloadBuilderAttributes) -> Self { + Self { payload_attributes: value, ..Default::default() } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::str::FromStr; + use alloy_primitives::{address, b256, bytes, FixedBytes, U256}; + use alloy_rpc_types_engine::PayloadAttributes; + use reth_payload_primitives::EngineApiMessageVersion; + + #[test] + fn test_payload_id() { + let expected = + PayloadId::new(FixedBytes::<8>::from_str("0x036369370c155d4c").unwrap().into()); + let attrs = ScrollPayloadAttributes { + payload_attributes: PayloadAttributes { + timestamp: 1728933301, + prev_randao: b256!("9158595abbdab2c90635087619aa7042bbebe47642dfab3c9bfb934f6b082765"), + suggested_fee_recipient: address!("4200000000000000000000000000000000000011"), + withdrawals: Some([].into()), + parent_beacon_block_root: b256!("8fe0193b9bf83cb7e5a08538e494fecc23046aab9a497af3704f4afdae3250ff").into(), + }, + transactions: Some([bytes!("7ef8f8a0dc19cfa777d90980e4875d0a548a881baaa3f83f14d1bc0d3038bc329350e54194deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8a4440a5e20000f424000000000000000000000000300000000670d6d890000000000000125000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000014bf9181db6e381d4384bbf69c48b0ee0eed23c6ca26143c6d2544f9d39997a590000000000000000000000007f83d659683caf2767fd3c720981d51f5bc365bc")].into()), + no_tx_pool: false, + block_data_hint: BlockDataHint{ + extra_data: Some(bytes!("476574682f76312e302e302f6c696e75782f676f312e342e32")), + state_root: Some(b256!("0x000000000000000000000000000000000000000000000000000000000000dead")), + coinbase: Some(address!("0x000000000000000000000000000000000000dead")), + nonce: Some(u64::MAX), + difficulty: Some(U256::from(10)) + }, + gas_limit: Some(10_000_000), + }; + + assert_eq!( + expected, + payload_id_scroll( + &b256!("3533bf30edaf9505d0810bf475cbe4e5f4b9889904b9845e83efdeab4e92eb1e"), + &attrs, + EngineApiMessageVersion::V3 as u8 + ) + ); + } +}
diff --git reth/crates/scroll/engine-primitives/src/payload/built.rs scroll-reth/crates/scroll/engine-primitives/src/payload/built.rs new file mode 100644 index 0000000000000000000000000000000000000000..cea13302aa1c9cfacba619cebd2b2f90613e6593 --- /dev/null +++ scroll-reth/crates/scroll/engine-primitives/src/payload/built.rs @@ -0,0 +1,135 @@ +//! Outcome of a Scroll block building task with payload attributes provided via the Engine API. + +use core::iter; +use std::sync::Arc; + +use alloy_eips::eip7685::Requests; +use alloy_primitives::U256; +use alloy_rpc_types_engine::{ + BlobsBundleV1, ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, + ExecutionPayloadEnvelopeV4, ExecutionPayloadFieldV2, ExecutionPayloadV1, ExecutionPayloadV3, + PayloadId, +}; +use reth_chain_state::ExecutedBlockWithTrieUpdates; +use reth_payload_primitives::BuiltPayload; +use reth_primitives_traits::SealedBlock; +use reth_scroll_primitives::{ScrollBlock, ScrollPrimitives}; + +/// Contains the built payload. +#[derive(Debug, Clone, Default)] +pub struct ScrollBuiltPayload { + /// Identifier of the payload + pub(crate) id: PayloadId, + /// Sealed block + pub(crate) block: Arc<SealedBlock<ScrollBlock>>, + /// Block execution data for the payload + pub(crate) executed_block: Option<ExecutedBlockWithTrieUpdates<ScrollPrimitives>>, + /// The fees of the block + pub(crate) fees: U256, +} + +impl ScrollBuiltPayload { + /// Initializes the payload with the given initial block. + pub const fn new( + id: PayloadId, + block: Arc<SealedBlock<ScrollBlock>>, + executed_block: Option<ExecutedBlockWithTrieUpdates<ScrollPrimitives>>, + fees: U256, + ) -> Self { + Self { id, block, executed_block, fees } + } + + /// Returns the identifier of the payload. + pub const fn id(&self) -> PayloadId { + self.id + } + + /// Returns the built block(sealed) + pub fn block(&self) -> &SealedBlock<ScrollBlock> { + &self.block + } + + /// Fees of the block + pub const fn fees(&self) -> U256 { + self.fees + } + + /// Converts the value into [`SealedBlock`]. + pub fn into_sealed_block(self) -> SealedBlock<ScrollBlock> { + Arc::unwrap_or_clone(self.block) + } +} + +impl BuiltPayload for ScrollBuiltPayload { + type Primitives = ScrollPrimitives; + + fn block(&self) -> &SealedBlock<ScrollBlock> { + self.block() + } + + fn fees(&self) -> U256 { + self.fees + } + + fn executed_block(&self) -> Option<ExecutedBlockWithTrieUpdates<Self::Primitives>> { + self.executed_block.clone() + } + + fn requests(&self) -> Option<Requests> { + None + } +} + +// V1 engine_getPayloadV1 response +impl From<ScrollBuiltPayload> for ExecutionPayloadV1 { + fn from(value: ScrollBuiltPayload) -> Self { + Self::from_block_unchecked( + value.block().hash(), + &Arc::unwrap_or_clone(value.block).into_block(), + ) + } +} + +// V2 engine_getPayloadV2 response +impl From<ScrollBuiltPayload> for ExecutionPayloadEnvelopeV2 { + fn from(value: ScrollBuiltPayload) -> Self { + let ScrollBuiltPayload { block, fees, .. } = value; + + Self { + block_value: fees, + execution_payload: ExecutionPayloadFieldV2::from_block_unchecked( + block.hash(), + &Arc::unwrap_or_clone(block).into_block(), + ), + } + } +} + +impl From<ScrollBuiltPayload> for ExecutionPayloadEnvelopeV3 { + fn from(value: ScrollBuiltPayload) -> Self { + let ScrollBuiltPayload { block, fees, .. } = value; + + Self { + execution_payload: ExecutionPayloadV3::from_block_unchecked( + block.hash(), + &Arc::unwrap_or_clone(block).into_block(), + ), + block_value: fees, + // From the engine API spec: + // + // > Client software **MAY** use any heuristics to decide whether to set + // `shouldOverrideBuilder` flag or not. If client software does not implement any + // heuristic this flag **SHOULD** be set to `false`. + // + // Spec: + // <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification-2> + should_override_builder: false, + blobs_bundle: BlobsBundleV1::new(iter::empty()), + } + } +} +impl From<ScrollBuiltPayload> for ExecutionPayloadEnvelopeV4 { + fn from(value: ScrollBuiltPayload) -> Self { + Self { envelope_inner: value.into(), execution_requests: Default::default() } + } +}
diff --git reth/crates/scroll/engine-primitives/src/payload/mod.rs scroll-reth/crates/scroll/engine-primitives/src/payload/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..c7fd48170173de33c7b2cab42bad7690f31c62c2 --- /dev/null +++ scroll-reth/crates/scroll/engine-primitives/src/payload/mod.rs @@ -0,0 +1,331 @@ +//! Engine API Payload types. + +mod attributes; +pub use attributes::ScrollPayloadBuilderAttributes; + +mod built; +pub use built::ScrollBuiltPayload; + +use alloc::{sync::Arc, vec::Vec}; +use core::marker::PhantomData; + +use alloy_consensus::{proofs, EMPTY_OMMER_ROOT_HASH}; +use alloy_eips::eip2718::Decodable2718; +use alloy_primitives::U256; +use alloy_rlp::BufMut; +use alloy_rpc_types_engine::{ + ExecutionData, ExecutionPayload, ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, + ExecutionPayloadEnvelopeV4, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, + PayloadError, +}; +use reth_engine_primitives::EngineTypes; +use reth_payload_primitives::{BuiltPayload, PayloadTypes}; +use reth_primitives::{Block, BlockBody, Header}; +use reth_primitives_traits::{NodePrimitives, SealedBlock}; +use reth_scroll_primitives::ScrollBlock; +use scroll_alloy_hardforks::ScrollHardforks; +use scroll_alloy_rpc_types_engine::ScrollPayloadAttributes; + +/// The types used in the default Scroll beacon consensus engine. +#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] +#[non_exhaustive] +pub struct ScrollEngineTypes<T: PayloadTypes = ScrollPayloadTypes> { + _marker: PhantomData<T>, +} + +impl< + T: PayloadTypes< + ExecutionData = ExecutionData, + BuiltPayload: BuiltPayload<Primitives: NodePrimitives<Block = ScrollBlock>>, + >, + > PayloadTypes for ScrollEngineTypes<T> +{ + type ExecutionData = T::ExecutionData; + type BuiltPayload = T::BuiltPayload; + type PayloadAttributes = T::PayloadAttributes; + type PayloadBuilderAttributes = T::PayloadBuilderAttributes; + + fn block_to_payload( + block: SealedBlock< + <<Self::BuiltPayload as BuiltPayload>::Primitives as NodePrimitives>::Block, + >, + ) -> ExecutionData { + let (payload, sidecar) = + ExecutionPayload::from_block_unchecked(block.hash(), &block.into_block()); + ExecutionData { payload, sidecar } + } +} + +impl<T> EngineTypes for ScrollEngineTypes<T> +where + T: PayloadTypes<ExecutionData = ExecutionData>, + T::BuiltPayload: BuiltPayload<Primitives: NodePrimitives<Block = ScrollBlock>> + + TryInto<ExecutionPayloadV1> + + TryInto<ExecutionPayloadEnvelopeV2> + + TryInto<ExecutionPayloadEnvelopeV3> + + TryInto<ExecutionPayloadEnvelopeV4>, +{ + type ExecutionPayloadEnvelopeV1 = ExecutionPayloadV1; + type ExecutionPayloadEnvelopeV2 = ExecutionPayloadEnvelopeV2; + type ExecutionPayloadEnvelopeV3 = ExecutionPayloadEnvelopeV3; + type ExecutionPayloadEnvelopeV4 = ExecutionPayloadEnvelopeV4; + type ExecutionPayloadEnvelopeV5 = ExecutionPayloadEnvelopeV4; +} + +/// A default payload type for [`ScrollEngineTypes`] +#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] +#[non_exhaustive] +pub struct ScrollPayloadTypes; + +impl PayloadTypes for ScrollPayloadTypes { + type ExecutionData = ExecutionData; + type BuiltPayload = ScrollBuiltPayload; + type PayloadAttributes = ScrollPayloadAttributes; + type PayloadBuilderAttributes = ScrollPayloadBuilderAttributes; + + fn block_to_payload( + block: SealedBlock< + <<Self::BuiltPayload as BuiltPayload>::Primitives as NodePrimitives>::Block, + >, + ) -> Self::ExecutionData { + let (payload, sidecar) = + ExecutionPayload::from_block_unchecked(block.hash(), &block.into_block()); + ExecutionData { payload, sidecar } + } +} + +/// Tries to create a new unsealed block from the given payload, sidecar and chain specification. +/// Sets the base fee of the block to `None` before the Curie hardfork. +/// Scroll implementation of the [`ExecutionPayload::try_into_block`], which will fail with +/// [`PayloadError::ExtraData`] due to the Scroll blocks containing extra data for the Clique +/// consensus. +pub fn try_into_block<T: Decodable2718, CS: ScrollHardforks>( + value: ExecutionData, + chainspec: Arc<CS>, +) -> Result<Block<T>, PayloadError> { + let mut block = match value.payload { + ExecutionPayload::V1(payload) => try_payload_v1_to_block(payload, chainspec)?, + ExecutionPayload::V2(payload) => try_payload_v2_to_block(payload, chainspec)?, + ExecutionPayload::V3(payload) => try_payload_v3_to_block(payload, chainspec)?, + }; + + block.header.parent_beacon_block_root = value.sidecar.parent_beacon_block_root(); + block.header.requests_hash = value.sidecar.requests_hash(); + + Ok(block) +} + +/// Tries to convert an [`ExecutionPayloadV1`] to [`Block`]. +fn try_payload_v1_to_block<T: Decodable2718, CS: ScrollHardforks>( + payload: ExecutionPayloadV1, + chainspec: CS, +) -> Result<Block<T>, PayloadError> { + // WARNING: It’s allowed for a base fee in EIP1559 to increase unbounded. We assume that + // it will fit in an u64. This is not always necessarily true, although it is extremely + // unlikely not to be the case, a u64 maximum would have 2^64 which equates to 18 ETH per + // gas. + let basefee = chainspec + .is_curie_active_at_block(payload.block_number) + .then_some(payload.base_fee_per_gas) + .map(|b| b.try_into()) + .transpose() + .map_err(|_| PayloadError::BaseFee(payload.base_fee_per_gas))?; + + let transactions = payload + .transactions + .iter() + .map(|tx| { + let mut buf = tx.as_ref(); + + let tx = T::decode_2718(&mut buf).map_err(alloy_rlp::Error::from)?; + + if !buf.is_empty() { + return Err(alloy_rlp::Error::UnexpectedLength); + } + + Ok(tx) + }) + .collect::<Result<Vec<_>, _>>()?; + + // Reuse the encoded bytes for root calculation + let transactions_root = + proofs::ordered_trie_root_with_encoder(&payload.transactions, |item, buf| { + buf.put_slice(item) + }); + + let header = Header { + parent_hash: payload.parent_hash, + beneficiary: payload.fee_recipient, + state_root: payload.state_root, + transactions_root, + receipts_root: payload.receipts_root, + withdrawals_root: None, + logs_bloom: payload.logs_bloom, + number: payload.block_number, + gas_limit: payload.gas_limit, + gas_used: payload.gas_used, + timestamp: payload.timestamp, + mix_hash: payload.prev_randao, + base_fee_per_gas: basefee, + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, + requests_hash: None, + extra_data: payload.extra_data, + // Defaults + ommers_hash: EMPTY_OMMER_ROOT_HASH, + difficulty: U256::ONE, + nonce: Default::default(), + }; + + Ok(Block { header, body: BlockBody { transactions, ..Default::default() } }) +} + +/// Tries to convert an [`ExecutionPayloadV2`] to [`Block`]. +fn try_payload_v2_to_block<T: Decodable2718, CS: ScrollHardforks>( + payload: ExecutionPayloadV2, + chainspec: CS, +) -> Result<Block<T>, PayloadError> { + // this performs the same conversion as the underlying V1 payload, but calculates the + // withdrawals root and adds withdrawals + let mut base_sealed_block = try_payload_v1_to_block(payload.payload_inner, chainspec)?; + let withdrawals_root = proofs::calculate_withdrawals_root(&payload.withdrawals); + base_sealed_block.body.withdrawals = Some(payload.withdrawals.into()); + base_sealed_block.header.withdrawals_root = Some(withdrawals_root); + Ok(base_sealed_block) +} + +/// Tries to convert an [`ExecutionPayloadV3`] to [`Block`]. +fn try_payload_v3_to_block<T: Decodable2718, CS: ScrollHardforks>( + payload: ExecutionPayloadV3, + chainspec: CS, +) -> Result<Block<T>, PayloadError> { + // this performs the same conversion as the underlying V2 payload, but inserts the blob gas + // used and excess blob gas + let mut base_block = try_payload_v2_to_block(payload.payload_inner, chainspec)?; + + base_block.header.blob_gas_used = Some(payload.blob_gas_used); + base_block.header.excess_blob_gas = Some(payload.excess_blob_gas); + + Ok(base_block) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{Address, Bloom, B256, U256}; + use alloy_rpc_types_engine::ExecutionPayloadV1; + use arbitrary::{Arbitrary, Unstructured}; + use rand::Rng; + use reth_scroll_chainspec::SCROLL_MAINNET; + use reth_scroll_primitives::ScrollTransactionSigned; + + #[test] + fn test_can_convert_execution_v1_payload_into_block() -> eyre::Result<()> { + let mut bytes = [0u8; 1024]; + rand::rng().fill(bytes.as_mut_slice()); + let mut u = Unstructured::new(&bytes); + + let mut extra_data = [0u8; 64]; + rand::rng().fill(extra_data.as_mut_slice()); + + let execution_payload = ExecutionPayload::V1(ExecutionPayloadV1 { + parent_hash: B256::random(), + fee_recipient: Address::random(), + state_root: B256::random(), + receipts_root: B256::random(), + logs_bloom: Bloom::random(), + prev_randao: B256::random(), + block_number: u64::arbitrary(&mut u)?, + gas_limit: u64::arbitrary(&mut u)?, + gas_used: u64::arbitrary(&mut u)?, + timestamp: u64::arbitrary(&mut u)?, + extra_data: extra_data.into(), + base_fee_per_gas: U256::from(u64::arbitrary(&mut u)?), + block_hash: B256::random(), + transactions: vec![], + }); + let execution_data = ExecutionData::new(execution_payload, Default::default()); + + let _: Block<ScrollTransactionSigned> = + try_into_block(execution_data, SCROLL_MAINNET.clone())?; + + Ok(()) + } + + #[test] + fn test_can_convert_execution_v2_payload_into_block() -> eyre::Result<()> { + let mut bytes = [0u8; 1024]; + rand::rng().fill(bytes.as_mut_slice()); + let mut u = Unstructured::new(&bytes); + + let mut extra_data = [0u8; 64]; + rand::rng().fill(extra_data.as_mut_slice()); + + let execution_payload = ExecutionPayload::V2(ExecutionPayloadV2 { + payload_inner: ExecutionPayloadV1 { + parent_hash: B256::random(), + fee_recipient: Address::random(), + state_root: B256::random(), + receipts_root: B256::random(), + logs_bloom: Bloom::random(), + prev_randao: B256::random(), + block_number: u64::arbitrary(&mut u)?, + gas_limit: u64::arbitrary(&mut u)?, + gas_used: u64::arbitrary(&mut u)?, + timestamp: u64::arbitrary(&mut u)?, + extra_data: extra_data.into(), + base_fee_per_gas: U256::from(u64::arbitrary(&mut u)?), + block_hash: B256::random(), + transactions: vec![], + }, + withdrawals: vec![], + }); + let execution_data = ExecutionData::new(execution_payload, Default::default()); + + let _: Block<ScrollTransactionSigned> = + try_into_block(execution_data, SCROLL_MAINNET.clone())?; + + Ok(()) + } + + #[test] + fn test_can_convert_execution_v3_payload_into_block() -> eyre::Result<()> { + let mut bytes = [0u8; 1024]; + rand::rng().fill(bytes.as_mut_slice()); + let mut u = Unstructured::new(&bytes); + + let mut extra_data = [0u8; 64]; + rand::rng().fill(extra_data.as_mut_slice()); + + let execution_payload = ExecutionPayload::V3(ExecutionPayloadV3 { + payload_inner: ExecutionPayloadV2 { + payload_inner: ExecutionPayloadV1 { + parent_hash: B256::random(), + fee_recipient: Address::random(), + state_root: B256::random(), + receipts_root: B256::random(), + logs_bloom: Bloom::random(), + prev_randao: B256::random(), + block_number: u64::arbitrary(&mut u)?, + gas_limit: u64::arbitrary(&mut u)?, + gas_used: u64::arbitrary(&mut u)?, + timestamp: u64::arbitrary(&mut u)?, + extra_data: extra_data.into(), + base_fee_per_gas: U256::from(u64::arbitrary(&mut u)?), + block_hash: B256::random(), + transactions: vec![], + }, + withdrawals: vec![], + }, + blob_gas_used: 0, + excess_blob_gas: 0, + }); + let execution_data = ExecutionData::new(execution_payload, Default::default()); + + let _: Block<ScrollTransactionSigned> = + try_into_block(execution_data, SCROLL_MAINNET.clone())?; + + Ok(()) + } +}
diff --git reth/crates/scroll/evm/Cargo.toml scroll-reth/crates/scroll/evm/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..e1cd82d674b5525ca5875f6f7589a5ace6ddd959 --- /dev/null +++ scroll-reth/crates/scroll/evm/Cargo.toml @@ -0,0 +1,79 @@ +[package] +name = "reth-scroll-evm" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# reth +reth-chainspec.workspace = true +reth-evm = { workspace = true, features = ["scroll-alloy-traits"] } +reth-execution-types.workspace = true +reth-primitives = { workspace = true, features = ["serde-bincode-compat"] } +reth-primitives-traits.workspace = true +reth-storage-api.workspace = true + +# revm +revm = { workspace = true, features = ["optional_no_base_fee"] } +revm-primitives.workspace = true +revm-scroll.workspace = true + +# scroll +reth-scroll-chainspec.workspace = true +reth-scroll-forks.workspace = true +reth-scroll-primitives = { workspace = true, features = ["serde", "serde-bincode-compat"], default-features = false } + +# alloy +alloy-consensus.workspace = true +alloy-eips.workspace = true +alloy-evm.workspace = true +alloy-primitives.workspace = true +alloy-rpc-types-engine.workspace = true + +# scroll +scroll-alloy-consensus.workspace = true +scroll-alloy-evm.workspace = true +scroll-alloy-hardforks.workspace = true + +# misc +derive_more.workspace = true +thiserror.workspace = true +tracing.workspace = true + +[dev-dependencies] +alloy-primitives = { workspace = true, features = ["getrandom", "rand"] } +eyre.workspace = true + +[features] +std = [ + "scroll-alloy-consensus/std", + "scroll-alloy-evm/std", + "alloy-consensus/std", + "alloy-evm/std", + "alloy-eips/std", + "alloy-primitives/std", + "derive_more/std", + "reth-chainspec/std", + "reth-evm/std", + "reth-execution-types/std", + "reth-primitives-traits/std", + "reth-primitives/std", + "reth-scroll-chainspec/std", + "reth-scroll-forks/std", + "reth-scroll-primitives/std", + "revm-primitives/std", + "revm-scroll/std", + "revm/std", + "thiserror/std", + "tracing/std", + "scroll-alloy-hardforks/std", + "reth-storage-api/std", + "alloy-rpc-types-engine/std", +]
diff --git reth/crates/scroll/evm/src/base_fee.rs scroll-reth/crates/scroll/evm/src/base_fee.rs new file mode 100644 index 0000000000000000000000000000000000000000..61d67b5bd45b490e5ecbbb797cf9f46d890c9976 --- /dev/null +++ scroll-reth/crates/scroll/evm/src/base_fee.rs @@ -0,0 +1,271 @@ +use alloy_consensus::BlockHeader; +use alloy_eips::calc_next_block_base_fee; +use alloy_primitives::U256; +use reth_chainspec::EthChainSpec; +use reth_scroll_chainspec::{ChainConfig, ScrollChainConfig}; +use reth_storage_api::{BaseFeeProvider, StorageProvider}; +use scroll_alloy_evm::curie::L1_GAS_PRICE_ORACLE_ADDRESS; +use scroll_alloy_hardforks::ScrollHardforks; + +/// L1 gas price oracle base fee slot. +pub const L1_BASE_FEE_SLOT: U256 = U256::from_limbs([1, 0, 0, 0]); + +/// Protocol-enforced maximum L2 base fee. +pub const MAX_L2_BASE_FEE: u64 = 10_000_000_000; + +/// The base fee overhead slot. +const L2_BASE_FEE_OVERHEAD_SLOT: U256 = U256::from_limbs([101, 0, 0, 0]); + +/// The default base fee overhead, in case the L2 system contract isn't deployed or +/// initialized. +pub const DEFAULT_BASE_FEE_OVERHEAD: U256 = U256::from_limbs([15_680_000, 0, 0, 0]); + +/// The base fee scalar slot. +const L2_BASE_FEE_SCALAR_SLOT: U256 = U256::from_limbs([102, 0, 0, 0]); + +/// The default scalar applied on the L1 base fee, in case the L2 system contract isn't deployed or +/// initialized. +pub const DEFAULT_BASE_FEE_SCALAR: U256 = U256::from_limbs([34_000_000_000_000, 0, 0, 0]); + +/// The precision of the L1 base fee. +pub const L1_BASE_FEE_PRECISION: U256 = U256::from_limbs([1_000_000_000_000_000_000, 0, 0, 0]); + +/// The initial base fee. +const INITIAL_BASE_FEE: u64 = 10_000_000; + +/// The Scroll base fee provider implementation. +#[derive(Clone, Debug, Default)] +pub struct ScrollBaseFeeProvider<ChainSpec>(ChainSpec); + +impl<ChainSpec> ScrollBaseFeeProvider<ChainSpec> { + /// Returns a new instance of a [`ScrollBaseFeeProvider`]. + pub const fn new(chain_spec: ChainSpec) -> Self { + Self(chain_spec) + } +} + +impl<ChainSpec, P> BaseFeeProvider<P> for ScrollBaseFeeProvider<ChainSpec> +where + ChainSpec: EthChainSpec + ScrollHardforks + ChainConfig<Config = ScrollChainConfig>, + P: StorageProvider, +{ + fn next_block_base_fee<H: BlockHeader>( + &self, + provider: &mut P, + parent_header: &H, + ts: u64, + ) -> Result<u64, P::Error> { + let chain_spec = &self.0; + + // Return early if Curie isn't active. This branch will be taken by the + // `ScrollPayloadBuilder` when executing `PayloadAttributes` that were derived from the L1 + // (during the L1 consolidation phase of the Rollup Node). + if !chain_spec.is_curie_active_at_block(parent_header.number() + 1) { + return Ok(0); + } + + // load l2 system config contract into cache. + let system_config_contract_address = + chain_spec.chain_config().l1_config.l2_system_config_address; + // query scalar and overhead. + let (mut scalar, mut overhead) = ( + provider.storage(system_config_contract_address, L2_BASE_FEE_SCALAR_SLOT)?, + provider.storage(system_config_contract_address, L2_BASE_FEE_OVERHEAD_SLOT)?, + ); + // if any value is 0, use the default values. + (scalar, overhead) = ( + if scalar == U256::ZERO { DEFAULT_BASE_FEE_SCALAR } else { scalar }, + if overhead == U256::ZERO { DEFAULT_BASE_FEE_OVERHEAD } else { overhead }, + ); + + let mut base_fee = if chain_spec.is_feynman_active_at_timestamp(ts) { + feynman_base_fee(chain_spec, parent_header, ts, overhead.saturating_to()) + } else { + let parent_l1_base_fee = + provider.storage(L1_GAS_PRICE_ORACLE_ADDRESS, L1_BASE_FEE_SLOT)?; + pre_feynman_base_fee(parent_l1_base_fee, scalar, overhead).saturating_to() + }; + + if base_fee > MAX_L2_BASE_FEE { + base_fee = MAX_L2_BASE_FEE; + } + + Ok(base_fee) + } +} + +/// Returns the Feynman base fee. +fn feynman_base_fee<H: BlockHeader, ChainSpec: EthChainSpec + ScrollHardforks>( + chainspec: ChainSpec, + parent_header: H, + ts: u64, + overhead: u64, +) -> u64 { + let eip_1559_base_fee = if chainspec.is_feynman_active_at_timestamp(parent_header.timestamp()) { + // extract the eip 1559 base fee from parent header by subtracting overhead from it. + let parent_eip_1559_base_fee = + parent_header.base_fee_per_gas().expect("Feynman active").saturating_sub(overhead); + let base_fee_params = chainspec.base_fee_params_at_timestamp(ts); + calc_next_block_base_fee( + parent_header.gas_used(), + parent_header.gas_limit(), + parent_eip_1559_base_fee, + base_fee_params, + ) + } else { + // this is the first Feynman block. + // if the parent has a base fee, return it. + if let Some(base_fee) = parent_header.base_fee_per_gas() { + base_fee + } else { + INITIAL_BASE_FEE + } + }; + + eip_1559_base_fee.saturating_add(overhead) +} + +/// Returns the pre Feynman base fee. +fn pre_feynman_base_fee(parent_l1_base_fee: U256, scalar: U256, overhead: U256) -> U256 { + // l1 base fee * scalar / precision + overhead. + parent_l1_base_fee * scalar / L1_BASE_FEE_PRECISION + overhead +} + +#[cfg(test)] +mod tests { + use super::*; + use std::boxed::Box; + + use alloy_consensus::BlockHeader; + use reth_scroll_chainspec::SCROLL_MAINNET; + use revm::database::{states::plain_account::PlainStorage, EmptyDB, State}; + use scroll_alloy_hardforks::ScrollHardfork; + + const CURIE_PARAMS_TEST_CASES: [(u64, u64); 8] = [ + (0u64, 15680000u64), + (1000000000, 15714000), + (2000000000, 15748000), + (100000000000, 19080000), + (111111111111, 19457777), + (2164000000000, 89256000), + (644149677419355, 10000000000), + (0x1c3c0f442u64, 15937691), + ]; + + const OVERWRITTEN_PARAMS_TEST_CASES: [(u64, u64); 7] = [ + (0, 1), + (1000000000, 1), + (2000000000, 1), + (100000000000, 2), + (111111111111, 2), + (2164000000000, 22), + (644149677419355, 6442), + ]; + + const EIP_1559_TEST_CASES: [(u64, u64, u64, u64); 3] = [ + (1000000000, 20000000, 10000000, 1000000000), // usage == target + (1000000001, 20000000, 9000000, 987500001), // usage below target + (1000000001, 20000000, 11000000, 1012500001), // usage above target + ]; + + const CURIE_TIMESTAMP: u64 = 1719994280; + const CURIE_BLOCK: u64 = 7096836; + + #[test] + fn test_should_return_correct_base_fee() -> Result<(), Box<dyn core::error::Error>> { + // init the state db. + let db = EmptyDB::new(); + let mut state = + State::builder().with_database(db).with_bundle_update().without_state_clear().build(); + + // init the provider and parent header. + let base_fee_provider = ScrollBaseFeeProvider::new(SCROLL_MAINNET.clone()); + let parent_header = alloy_consensus::Header { + timestamp: CURIE_TIMESTAMP, + number: CURIE_BLOCK, + ..Default::default() + }; + let parent_header_ts = parent_header.timestamp(); + + // helper closure to insert the l1 base fee in state. + let insert_l1_base_fee = |state: &mut State<EmptyDB>, l1_base_fee: u64| { + let oracle_storage_pre_fork = + PlainStorage::from_iter([(L1_BASE_FEE_SLOT, U256::from(l1_base_fee))]); + state.insert_account_with_storage( + L1_GAS_PRICE_ORACLE_ADDRESS, + Default::default(), + oracle_storage_pre_fork, + ); + }; + + // for each test case, insert the l1 base fee and check the expected value matches. + for (l1_base_fee, expected_base_fee) in CURIE_PARAMS_TEST_CASES { + insert_l1_base_fee(&mut state, l1_base_fee); + + // fetch base fee from db. + let base_fee = base_fee_provider.next_block_base_fee( + &mut state, + &parent_header, + parent_header_ts + 1, + )?; + assert_eq!(base_fee, expected_base_fee); + } + + // insert the base fee params. + let system_contract_storage = PlainStorage::from_iter([ + (L2_BASE_FEE_SCALAR_SLOT, U256::from(10000000)), + (L2_BASE_FEE_OVERHEAD_SLOT, U256::ONE), + ]); + state.insert_account_with_storage( + SCROLL_MAINNET.config.l1_config.l2_system_config_address, + Default::default(), + system_contract_storage, + ); + + // for each test case, insert the l1 base fee and check the expected value matches. + for (l1_base_fee, expected_base_fee) in OVERWRITTEN_PARAMS_TEST_CASES { + insert_l1_base_fee(&mut state, l1_base_fee); + + // fetch base fee from db. + let base_fee = base_fee_provider.next_block_base_fee( + &mut state, + &parent_header, + parent_header_ts + 1, + )?; + assert_eq!(base_fee, expected_base_fee); + } + + // update the parent header used to activate Feynman. + let feynman_fork_ts = SCROLL_MAINNET + .hardforks + .get(ScrollHardfork::Feynman) + .unwrap() + .as_timestamp() + .expect("Feynman is timestamp based forked."); + let mut parent_header = alloy_consensus::Header { + timestamp: feynman_fork_ts + 1, + number: CURIE_BLOCK + 1, + ..Default::default() + }; + let parent_header_ts = parent_header.timestamp(); + + // for each test case, update the parent header fields and check the expected value matches. + for (parent_base_fee, parent_gas_limit, parent_gas_used, expected_base_fee) in + EIP_1559_TEST_CASES + { + parent_header.base_fee_per_gas = Some(parent_base_fee); + parent_header.gas_limit = parent_gas_limit; + parent_header.gas_used = parent_gas_used; + + // fetch base fee from db. + let base_fee = base_fee_provider.next_block_base_fee( + &mut state, + &parent_header, + parent_header_ts + 1, + )?; + assert_eq!(base_fee, expected_base_fee); + } + + Ok(()) + } +}
diff --git reth/crates/scroll/evm/src/build.rs scroll-reth/crates/scroll/evm/src/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..2645dad697b561a0d1e6c52d8b9dcc63a575ca74 --- /dev/null +++ scroll-reth/crates/scroll/evm/src/build.rs @@ -0,0 +1,94 @@ +use alloc::sync::Arc; +use alloy_consensus::{proofs, BlockBody, Header, TxReceipt, EMPTY_OMMER_ROOT_HASH}; +use alloy_eips::merge::BEACON_NONCE; +use alloy_evm::block::{BlockExecutionError, BlockExecutorFactory}; +use alloy_primitives::{logs_bloom, Address}; +use reth_evm::execute::{BlockAssembler, BlockAssemblerInput}; +use reth_execution_types::BlockExecutionResult; +use reth_primitives_traits::SignedTransaction; +use reth_scroll_primitives::ScrollReceipt; +use scroll_alloy_evm::ScrollBlockExecutionCtx; +use scroll_alloy_hardforks::ScrollHardforks; + +/// Block builder for Scroll. +#[derive(Debug)] +pub struct ScrollBlockAssembler<ChainSpec> { + chain_spec: Arc<ChainSpec>, +} + +impl<ChainSpec> ScrollBlockAssembler<ChainSpec> { + /// Creates a new [`ScrollBlockAssembler`]. + pub const fn new(chain_spec: Arc<ChainSpec>) -> Self { + Self { chain_spec } + } +} + +impl<ChainSpec> Clone for ScrollBlockAssembler<ChainSpec> { + fn clone(&self) -> Self { + Self { chain_spec: self.chain_spec.clone() } + } +} + +impl<F, ChainSpec> BlockAssembler<F> for ScrollBlockAssembler<ChainSpec> +where + ChainSpec: ScrollHardforks, + F: for<'a> BlockExecutorFactory< + ExecutionCtx<'a> = ScrollBlockExecutionCtx, + Transaction: SignedTransaction, + Receipt = ScrollReceipt, + >, +{ + type Block = alloy_consensus::Block<F::Transaction>; + + fn assemble_block( + &self, + input: BlockAssemblerInput<'_, '_, F>, + ) -> Result<Self::Block, BlockExecutionError> { + let BlockAssemblerInput { + evm_env, + execution_ctx: ctx, + transactions, + output: BlockExecutionResult { receipts, gas_used, .. }, + state_root, + .. + } = input; + + let timestamp = evm_env.block_env.timestamp; + + let transactions_root = proofs::calculate_transaction_root(&transactions); + let receipts_root = ScrollReceipt::calculate_receipt_root_no_memo(receipts); + let logs_bloom = logs_bloom(receipts.iter().flat_map(|r| r.logs())); + + let header = Header { + parent_hash: ctx.parent_hash, + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: Address::ZERO, + state_root, + transactions_root, + receipts_root, + withdrawals_root: None, + logs_bloom, + timestamp: timestamp.to(), + mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(), + nonce: BEACON_NONCE.into(), + base_fee_per_gas: self + .chain_spec + .is_curie_active_at_block(evm_env.block_env.number.to()) + .then_some(evm_env.block_env.basefee), + number: evm_env.block_env.number.to(), + gas_limit: evm_env.block_env.gas_limit, + difficulty: evm_env.block_env.difficulty, + gas_used: *gas_used, + extra_data: Default::default(), + parent_beacon_block_root: None, + blob_gas_used: None, + excess_blob_gas: None, + requests_hash: None, + }; + + Ok(alloy_consensus::Block::new( + header, + BlockBody { transactions, ommers: Default::default(), withdrawals: None }, + )) + } +}
diff --git reth/crates/scroll/evm/src/config.rs scroll-reth/crates/scroll/evm/src/config.rs new file mode 100644 index 0000000000000000000000000000000000000000..aee3f55fa81fdac19133e964e731c512a1c68e83 --- /dev/null +++ scroll-reth/crates/scroll/evm/src/config.rs @@ -0,0 +1,370 @@ +use crate::{build::ScrollBlockAssembler, ScrollEvmConfig, ScrollNextBlockEnvAttributes}; +use alloc::sync::Arc; + +use alloy_consensus::{BlockHeader, Header}; +use alloy_eips::{eip2718::WithEncoded, Decodable2718}; +use alloy_evm::{FromRecoveredTx, FromTxWithEncoded}; +use alloy_primitives::B256; +use alloy_rpc_types_engine::ExecutionData; +use core::convert::Infallible; +use reth_chainspec::EthChainSpec; +use reth_evm::{ + ConfigureEngineEvm, ConfigureEvm, EvmEnv, EvmEnvFor, ExecutableTxIterator, ExecutionCtxFor, +}; +use reth_primitives_traits::{ + BlockTy, NodePrimitives, SealedBlock, SealedHeader, SignedTransaction, TxTy, +}; +use reth_scroll_chainspec::{ChainConfig, ScrollChainConfig}; +use reth_scroll_primitives::ScrollReceipt; +use reth_storage_api::errors::any::AnyError; +use revm::{ + context::{BlockEnv, CfgEnv, TxEnv}, + primitives::U256, +}; +use revm_scroll::ScrollSpecId; +use scroll_alloy_evm::{ + ScrollBlockExecutionCtx, ScrollBlockExecutorFactory, ScrollPrecompilesFactory, + ScrollReceiptBuilder, ScrollTransactionIntoTxEnv, +}; +use scroll_alloy_hardforks::ScrollHardforks; + +impl<ChainSpec, N, R, P> ConfigureEvm for ScrollEvmConfig<ChainSpec, N, R, P> +where + ChainSpec: EthChainSpec + ChainConfig<Config = ScrollChainConfig> + ScrollHardforks, + N: NodePrimitives< + Receipt = R::Receipt, + SignedTx = R::Transaction, + BlockHeader = Header, + BlockBody = alloy_consensus::BlockBody<R::Transaction>, + Block = alloy_consensus::Block<R::Transaction>, + >, + ScrollTransactionIntoTxEnv<TxEnv>: + FromRecoveredTx<N::SignedTx> + FromTxWithEncoded<N::SignedTx>, + R: ScrollReceiptBuilder<Receipt = ScrollReceipt, Transaction: SignedTransaction>, + P: ScrollPrecompilesFactory, + Self: Send + Sync + Unpin + Clone + 'static, +{ + type Primitives = N; + type Error = Infallible; + type NextBlockEnvCtx = ScrollNextBlockEnvAttributes; + type BlockExecutorFactory = ScrollBlockExecutorFactory<R, Arc<ChainSpec>, P>; + type BlockAssembler = ScrollBlockAssembler<ChainSpec>; + + fn block_executor_factory(&self) -> &Self::BlockExecutorFactory { + &self.executor_factory + } + + fn block_assembler(&self) -> &Self::BlockAssembler { + &self.block_assembler + } + + fn evm_env(&self, header: &N::BlockHeader) -> EvmEnv<ScrollSpecId> { + let chain_spec = self.chain_spec(); + let spec_id = self.spec_id_at_timestamp_and_number(header.timestamp(), header.number()); + + let cfg_env = CfgEnv::<ScrollSpecId>::default() + .with_spec(spec_id) + .with_chain_id(chain_spec.chain().id()); + + // get coinbase from chain spec + let coinbase = if let Some(vault_address) = chain_spec.chain_config().fee_vault_address { + vault_address + } else { + header.beneficiary() + }; + + let block_env = BlockEnv { + number: U256::from(header.number()), + beneficiary: coinbase, + timestamp: U256::from(header.timestamp()), + difficulty: header.difficulty(), + prevrandao: header.mix_hash(), + gas_limit: header.gas_limit(), + basefee: header.base_fee_per_gas().unwrap_or_default(), + // EIP-4844 excess blob gas of this block, introduced in Cancun + blob_excess_gas_and_price: None, + }; + + EvmEnv { cfg_env, block_env } + } + + fn next_evm_env( + &self, + parent: &N::BlockHeader, + attributes: &Self::NextBlockEnvCtx, + ) -> Result<EvmEnv<ScrollSpecId>, Self::Error> { + // ensure we're not missing any timestamp based hardforks + let spec_id = + self.spec_id_at_timestamp_and_number(attributes.timestamp, parent.number() + 1); + + let chain_spec = self.chain_spec(); + + // configure evm env based on parent block + let cfg_env = CfgEnv::<ScrollSpecId>::default() + .with_chain_id(chain_spec.chain().id()) + .with_spec(spec_id); + + // get coinbase from chain spec + let coinbase = if let Some(vault_address) = chain_spec.chain_config().fee_vault_address { + vault_address + } else { + attributes.suggested_fee_recipient + }; + + let block_env = BlockEnv { + number: U256::from(parent.number() + 1), + beneficiary: coinbase, + timestamp: U256::from(attributes.timestamp), + difficulty: U256::ONE, + prevrandao: Some(B256::ZERO), + gas_limit: attributes.gas_limit, + basefee: attributes.base_fee, + blob_excess_gas_and_price: None, + }; + + Ok(EvmEnv { cfg_env, block_env }) + } + + fn context_for_block<'a>( + &self, + block: &'a SealedBlock<BlockTy<Self::Primitives>>, + ) -> ExecutionCtxFor<'a, Self> { + ScrollBlockExecutionCtx { parent_hash: block.header().parent_hash() } + } + + fn context_for_next_block( + &self, + parent: &SealedHeader<N::BlockHeader>, + _attributes: Self::NextBlockEnvCtx, + ) -> ExecutionCtxFor<'_, Self> { + ScrollBlockExecutionCtx { parent_hash: parent.hash() } + } +} + +impl<ChainSpec, N, R, P> ConfigureEngineEvm<ExecutionData> for ScrollEvmConfig<ChainSpec, N, R, P> +where + ChainSpec: EthChainSpec + ChainConfig<Config = ScrollChainConfig> + ScrollHardforks, + N: NodePrimitives< + Receipt = R::Receipt, + SignedTx = R::Transaction, + BlockHeader = Header, + BlockBody = alloy_consensus::BlockBody<R::Transaction>, + Block = alloy_consensus::Block<R::Transaction>, + >, + ScrollTransactionIntoTxEnv<TxEnv>: + FromRecoveredTx<N::SignedTx> + FromTxWithEncoded<N::SignedTx>, + R: ScrollReceiptBuilder<Receipt = ScrollReceipt, Transaction: SignedTransaction>, + P: ScrollPrecompilesFactory, + Self: Send + Sync + Unpin + Clone + 'static, +{ + fn evm_env_for_payload(&self, payload: &ExecutionData) -> EvmEnvFor<Self> { + let timestamp = payload.payload.timestamp(); + let block_number = payload.payload.block_number(); + let chain_spec = self.chain_spec(); + + let spec_id = self.spec_id_at_timestamp_and_number(timestamp, block_number); + + let cfg_env = CfgEnv::<ScrollSpecId>::default() + .with_chain_id(chain_spec.chain().id()) + .with_spec(spec_id); + + // get coinbase from chain config. + let coinbase = + if let Some(vault_address) = self.chain_spec().chain_config().fee_vault_address { + vault_address + } else { + payload.payload.as_v1().fee_recipient + }; + + let block_env = BlockEnv { + number: U256::from(block_number), + beneficiary: coinbase, + timestamp: U256::from(timestamp), + difficulty: U256::ONE, + prevrandao: Some(B256::ZERO), + gas_limit: payload.payload.as_v1().gas_limit, + basefee: payload.payload.as_v1().base_fee_per_gas.to(), + blob_excess_gas_and_price: None, + }; + + EvmEnv { cfg_env, block_env } + } + + fn context_for_payload<'a>(&self, payload: &'a ExecutionData) -> ExecutionCtxFor<'a, Self> { + ScrollBlockExecutionCtx { parent_hash: payload.parent_hash() } + } + + fn tx_iterator_for_payload(&self, payload: &ExecutionData) -> impl ExecutableTxIterator<Self> { + payload.payload.transactions().clone().into_iter().map(|encoded| { + let tx = TxTy::<Self::Primitives>::decode_2718_exact(encoded.as_ref()) + .map_err(AnyError::new)?; + let signer = tx.try_recover().map_err(AnyError::new)?; + Ok::<_, AnyError>(WithEncoded::new(encoded, tx.with_signer(signer))) + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ScrollRethReceiptBuilder; + use alloy_consensus::Header; + use reth_chainspec::{Head, NamedChain::Scroll}; + use reth_scroll_chainspec::{ScrollChainConfig, ScrollChainSpecBuilder}; + use reth_scroll_primitives::ScrollPrimitives; + use revm::primitives::B256; + use revm_primitives::Address; + + #[test] + fn test_spec_at_head() { + let config = ScrollEvmConfig::<_, ScrollPrimitives, _>::new( + ScrollChainSpecBuilder::scroll_mainnet().build(ScrollChainConfig::mainnet()).into(), + ScrollRethReceiptBuilder::default(), + ); + + // prepare all fork heads + let curie_head = &Head { number: 7096836, ..Default::default() }; + let bernoulli_head = &Head { number: 5220340, ..Default::default() }; + let pre_bernoulli_head = &Head { number: 0, ..Default::default() }; + + // check correct spec id + assert_eq!( + config.spec_id_at_timestamp_and_number(curie_head.timestamp, curie_head.number), + ScrollSpecId::CURIE + ); + assert_eq!( + config.spec_id_at_timestamp_and_number(bernoulli_head.timestamp, bernoulli_head.number), + ScrollSpecId::BERNOULLI + ); + assert_eq!( + config.spec_id_at_timestamp_and_number( + pre_bernoulli_head.timestamp, + pre_bernoulli_head.number + ), + ScrollSpecId::SHANGHAI + ); + } + + #[test] + fn test_fill_cfg_env() { + let config = ScrollEvmConfig::<_, ScrollPrimitives, _>::new( + ScrollChainSpecBuilder::scroll_mainnet().build(ScrollChainConfig::mainnet()).into(), + ScrollRethReceiptBuilder::default(), + ); + + // curie + let curie_header = Header { number: 7096836, ..Default::default() }; + + // fill cfg env + let env = config.evm_env(&curie_header); + + // check correct cfg env + assert_eq!(env.cfg_env.chain_id, Scroll as u64); + assert_eq!(env.cfg_env.spec, ScrollSpecId::CURIE); + + // bernoulli + let bernoulli_header = Header { number: 5220340, ..Default::default() }; + + // fill cfg env + let env = config.evm_env(&bernoulli_header); + + // check correct cfg env + assert_eq!(env.cfg_env.chain_id, Scroll as u64); + assert_eq!(env.cfg_env.spec, ScrollSpecId::BERNOULLI); + + // pre-bernoulli + let pre_bernoulli_header = Header { number: 0, ..Default::default() }; + + // fill cfg env + let env = config.evm_env(&pre_bernoulli_header); + + // check correct cfg env + assert_eq!(env.cfg_env.chain_id, Scroll as u64); + assert_eq!(env.cfg_env.spec, ScrollSpecId::SHANGHAI); + } + + #[test] + fn test_fill_block_env() { + let config = ScrollEvmConfig::<_, ScrollPrimitives, _>::new( + ScrollChainSpecBuilder::scroll_mainnet().build(ScrollChainConfig::mainnet()).into(), + ScrollRethReceiptBuilder::default(), + ); + + // curie header + let header = Header { + number: 7096836, + beneficiary: Address::random(), + timestamp: 1719994277, + mix_hash: B256::random(), + base_fee_per_gas: Some(155157341), + gas_limit: 10000000, + ..Default::default() + }; + + // fill block env + let env = config.evm_env(&header); + + // verify block env correctly updated + let expected = BlockEnv { + number: U256::from(header.number), + beneficiary: config.chain_spec().config.fee_vault_address.unwrap(), + timestamp: U256::from(header.timestamp), + prevrandao: Some(header.mix_hash), + difficulty: U256::ZERO, + basefee: header.base_fee_per_gas.unwrap_or_default(), + gas_limit: header.gas_limit, + blob_excess_gas_and_price: None, + }; + assert_eq!(env.block_env, expected) + } + + #[test] + fn test_next_cfg_and_block_env() -> eyre::Result<()> { + let config = ScrollEvmConfig::<_, ScrollPrimitives, _>::new( + ScrollChainSpecBuilder::scroll_mainnet().build(ScrollChainConfig::mainnet()).into(), + ScrollRethReceiptBuilder::default(), + ); + + // pre curie header + let header = Header { + number: 7096835, + beneficiary: Address::random(), + timestamp: 1719994274, + mix_hash: B256::random(), + base_fee_per_gas: None, + gas_limit: 10000000, + ..Default::default() + }; + + // curie block attributes + let attributes = ScrollNextBlockEnvAttributes { + timestamp: 1719994277, + suggested_fee_recipient: Address::random(), + gas_limit: 10000000, + base_fee: 155157341, + }; + + // get next cfg env and block env + let env = config.next_evm_env(&header, &attributes)?; + let (cfg_env, block_env, spec) = (env.cfg_env.clone(), env.block_env, env.cfg_env.spec); + + // verify cfg env + assert_eq!(cfg_env.chain_id, Scroll as u64); + assert_eq!(spec, ScrollSpecId::CURIE); + + // verify block env + let expected = BlockEnv { + number: U256::from(header.number + 1), + beneficiary: config.chain_spec().config.fee_vault_address.unwrap(), + timestamp: U256::from(attributes.timestamp), + prevrandao: Some(B256::ZERO), + difficulty: U256::ONE, + basefee: 155157341, + gas_limit: header.gas_limit, + blob_excess_gas_and_price: None, + }; + assert_eq!(block_env, expected); + + Ok(()) + } +}
diff --git reth/crates/scroll/evm/src/execute.rs scroll-reth/crates/scroll/evm/src/execute.rs new file mode 100644 index 0000000000000000000000000000000000000000..f37769fc9c02a9e9bf867f9500ff1f553333c8cc --- /dev/null +++ scroll-reth/crates/scroll/evm/src/execute.rs @@ -0,0 +1,669 @@ +//! Execution primitives for EVM. + +use crate::ScrollEvmConfig; +use core::fmt::Debug; + +use alloy_consensus::BlockHeader; +use alloy_primitives::{Address, B256}; +use reth_primitives::SealedBlock; +use reth_primitives_traits::Block; + +/// Input for block execution. +#[derive(Debug, Clone, Copy)] +pub struct ScrollBlockExecutionInput { + /// Block number. + pub number: u64, + /// Block timestamp. + pub timestamp: u64, + /// Parent block hash. + pub parent_hash: B256, + /// Block gas limit. + pub gas_limit: u64, + /// Block beneficiary. + pub beneficiary: Address, +} + +impl<B: Block> From<&SealedBlock<B>> for ScrollBlockExecutionInput { + fn from(block: &SealedBlock<B>) -> Self { + Self { + number: block.header().number(), + timestamp: block.header().timestamp(), + parent_hash: block.header().parent_hash(), + gas_limit: block.header().gas_limit(), + beneficiary: block.header().beneficiary(), + } + } +} + +/// Helper type with backwards compatible methods to obtain Scroll executor +/// providers. +pub type ScrollExecutorProvider = ScrollEvmConfig; + +#[cfg(test)] +mod tests { + use crate::{ScrollEvmConfig, ScrollRethReceiptBuilder}; + use std::{convert::Infallible, sync::Arc}; + + use alloy_consensus::{ + transaction::{Recovered, SignerRecoverable}, + Block, BlockBody, Header, SignableTransaction, Signed, Transaction, TxLegacy, + }; + use alloy_eips::{ + eip7702::{constants::PER_EMPTY_ACCOUNT_COST, Authorization, SignedAuthorization}, + Typed2718, + }; + use alloy_evm::{ + block::{BlockExecutionResult, BlockExecutor}, + precompiles::PrecompilesMap, + Evm, + }; + use alloy_primitives::Sealed; + use reth_chainspec::MIN_TRANSACTION_GAS; + use reth_evm::ConfigureEvm; + use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SignedTransaction}; + use reth_scroll_chainspec::{ScrollChainConfig, ScrollChainSpec, ScrollChainSpecBuilder}; + use reth_scroll_primitives::{ + ScrollBlock, ScrollPrimitives, ScrollReceipt, ScrollTransactionSigned, + }; + use revm::{ + bytecode::Bytecode, + database::{ + states::{bundle_state::BundleRetention, StorageSlot}, + EmptyDBTyped, State, + }, + inspector::NoOpInspector, + primitives::{Address, TxKind, B256, U256}, + state::AccountInfo, + }; + use scroll_alloy_consensus::{ScrollTransactionReceipt, ScrollTxEnvelope, ScrollTxType}; + use scroll_alloy_evm::{ + compute_compression_ratio, + curie::{ + BLOB_SCALAR_SLOT, COMMIT_SCALAR_SLOT, CURIE_L1_GAS_PRICE_ORACLE_BYTECODE, + CURIE_L1_GAS_PRICE_ORACLE_STORAGE, IS_CURIE_SLOT, L1_BLOB_BASE_FEE_SLOT, + L1_GAS_PRICE_ORACLE_ADDRESS, + }, + feynman::{IS_FEYNMAN_SLOT, PENALTY_FACTOR_SLOT, PENALTY_THRESHOLD_SLOT}, + ScrollBlockExecutionCtx, ScrollBlockExecutor, ScrollEvm, + }; + use scroll_alloy_hardforks::ScrollHardforks; + + const BLOCK_GAS_LIMIT: u64 = 10_000_000; + const SCROLL_CHAIN_ID: u64 = 534352; + const NOT_CURIE_BLOCK_NUMBER: u64 = 7096835; + const CURIE_BLOCK_NUMBER: u64 = 7096837; + const EUCLID_V2_BLOCK_NUMBER: u64 = 14907015; + const EUCLID_V2_BLOCK_TIMESTAMP: u64 = 1745305200; + const FEYNMAN_BLOCK_TIMESTAMP: u64 = 1755576000; + + const L1_BASE_FEE_SLOT: U256 = U256::from_limbs([1, 0, 0, 0]); + const OVER_HEAD_SLOT: U256 = U256::from_limbs([2, 0, 0, 0]); + const SCALAR_SLOT: U256 = U256::from_limbs([3, 0, 0, 0]); + + fn state() -> State<EmptyDBTyped<Infallible>> { + let db = EmptyDBTyped::<Infallible>::new(); + State::builder().with_database(db).with_bundle_update().without_state_clear().build() + } + + #[allow(clippy::type_complexity)] + fn executor<'a>( + block: &RecoveredBlock<ScrollBlock>, + state: &'a mut State<EmptyDBTyped<Infallible>>, + ) -> ScrollBlockExecutor< + ScrollEvm<&'a mut State<EmptyDBTyped<Infallible>>, NoOpInspector, PrecompilesMap>, + ScrollRethReceiptBuilder, + Arc<ScrollChainSpec>, + > { + let chain_spec = + Arc::new(ScrollChainSpecBuilder::scroll_mainnet().build(ScrollChainConfig::mainnet())); + let evm_config = ScrollEvmConfig::scroll(chain_spec.clone()); + + let evm = evm_config.evm_for_block(state, block.header()); + let receipt_builder = ScrollRethReceiptBuilder::default(); + ScrollBlockExecutor::new( + evm, + ScrollBlockExecutionCtx { parent_hash: block.parent_hash }, + chain_spec, + receipt_builder, + ) + } + + fn block( + number: u64, + timestamp: u64, + transactions: Vec<ScrollTransactionSigned>, + ) -> RecoveredBlock<<ScrollPrimitives as NodePrimitives>::Block> { + let senders = transactions.iter().map(|t| t.recover_signer().unwrap()).collect(); + RecoveredBlock::new_unhashed( + Block { + header: Header { + number, + timestamp, + gas_limit: BLOCK_GAS_LIMIT, + ..Default::default() + }, + body: BlockBody { transactions, ..Default::default() }, + }, + senders, + ) + } + + fn transaction(ty: ScrollTxType, gas_limit: u64) -> ScrollTxEnvelope { + let pk = B256::random(); + match ty { + ScrollTxType::Legacy => { + let tx = TxLegacy { + to: TxKind::Call(Address::ZERO), + chain_id: Some(SCROLL_CHAIN_ID), + gas_limit, + ..Default::default() + }; + let signature = reth_primitives::sign_message(pk, tx.signature_hash()).unwrap(); + ScrollTxEnvelope::Legacy(Signed::new_unhashed(tx, signature)) + } + ScrollTxType::Eip2930 => { + let tx = alloy_consensus::TxEip2930 { + to: TxKind::Call(Address::ZERO), + chain_id: SCROLL_CHAIN_ID, + gas_limit, + ..Default::default() + }; + let signature = reth_primitives::sign_message(pk, tx.signature_hash()).unwrap(); + ScrollTxEnvelope::Eip2930(Signed::new_unhashed(tx, signature)) + } + ScrollTxType::Eip1559 => { + let tx = alloy_consensus::TxEip1559 { + to: TxKind::Call(Address::ZERO), + chain_id: SCROLL_CHAIN_ID, + gas_limit, + ..Default::default() + }; + let signature = reth_primitives::sign_message(pk, tx.signature_hash()).unwrap(); + ScrollTxEnvelope::Eip1559(Signed::new_unhashed(tx, signature)) + } + ScrollTxType::Eip7702 => { + let authorization = Authorization { + chain_id: Default::default(), + address: Address::random(), + nonce: 0, + }; + let signature = + reth_primitives::sign_message(B256::random(), authorization.signature_hash()) + .unwrap(); + + let tx = alloy_consensus::TxEip7702 { + to: Address::ZERO, + chain_id: SCROLL_CHAIN_ID, + gas_limit: gas_limit + PER_EMPTY_ACCOUNT_COST, + authorization_list: vec![SignedAuthorization::new_unchecked( + authorization, + signature.v() as u8, + signature.r(), + signature.s(), + )], + ..Default::default() + }; + let signature = reth_primitives::sign_message(pk, tx.signature_hash()).unwrap(); + ScrollTxEnvelope::Eip7702(Signed::new_unhashed(tx, signature)) + } + ScrollTxType::L1Message => { + ScrollTxEnvelope::L1Message(Sealed::new(scroll_alloy_consensus::TxL1Message { + sender: Address::random(), + to: Address::ZERO, + gas_limit, + ..Default::default() + })) + } + } + } + + fn execute_block( + transactions: Vec<ScrollTxEnvelope>, + block_number: u64, + block_timestamp: u64, + compression_ratios: Option<Vec<U256>>, + ) -> eyre::Result<BlockExecutionResult<ScrollReceipt>> { + let block = block(block_number, block_timestamp, transactions); + + let mut state = state(); + let mut strategy = executor(&block, &mut state); + + // determine l1 gas oracle storage + let l1_gas_oracle_storage = + if strategy.spec().is_feynman_active_at_timestamp(block_timestamp) { + vec![ + (L1_BLOB_BASE_FEE_SLOT, U256::from(1000)), + (OVER_HEAD_SLOT, U256::from(1000)), + (SCALAR_SLOT, U256::from(1000)), + (L1_BLOB_BASE_FEE_SLOT, U256::from(10000)), + (COMMIT_SCALAR_SLOT, U256::from(1000)), + (BLOB_SCALAR_SLOT, U256::from(10000)), + (IS_CURIE_SLOT, U256::from(1)), + (PENALTY_THRESHOLD_SLOT, U256::from(1_000_000_000u64)), + (PENALTY_FACTOR_SLOT, U256::from(1_000_000_000u64)), + (IS_FEYNMAN_SLOT, U256::from(1)), + ] + } else if strategy.spec().is_curie_active_at_block(block_number) { + vec![ + (L1_BLOB_BASE_FEE_SLOT, U256::from(1000)), + (OVER_HEAD_SLOT, U256::from(1000)), + (SCALAR_SLOT, U256::from(1000)), + (L1_BLOB_BASE_FEE_SLOT, U256::from(10000)), + (COMMIT_SCALAR_SLOT, U256::from(1000)), + (BLOB_SCALAR_SLOT, U256::from(10000)), + (IS_CURIE_SLOT, U256::from(1)), + ] + } else { + vec![ + (L1_BASE_FEE_SLOT, U256::from(1000)), + (OVER_HEAD_SLOT, U256::from(1000)), + (SCALAR_SLOT, U256::from(1000)), + ] + } + .into_iter() + .collect(); + + // load accounts in state + strategy.evm_mut().db_mut().insert_account_with_storage( + L1_GAS_PRICE_ORACLE_ADDRESS, + Default::default(), + l1_gas_oracle_storage, + ); + for add in block.senders() { + strategy + .evm_mut() + .db_mut() + .insert_account(*add, AccountInfo { balance: U256::MAX, ..Default::default() }); + } + + if let Some(compression_ratios) = compression_ratios { + Ok(strategy.execute_block_with_compression_cache( + block.transactions_recovered(), + compression_ratios, + )?) + } else { + Ok(strategy.execute_block(block.transactions_recovered())?) + } + } + + fn execute_transaction( + tx_type: ScrollTxType, + block_number: u64, + block_timestamp: u64, + expected_l1_fee: U256, + expected_error: Option<&str>, + ) -> eyre::Result<()> { + // prepare transaction + let transaction = transaction(tx_type, MIN_TRANSACTION_GAS); + let block = block(block_number, block_timestamp, vec![transaction.clone()]); + + // init strategy + let mut state = state(); + let mut strategy = executor(&block, &mut state); + + // determine l1 gas oracle storage + let l1_gas_oracle_storage = + if strategy.spec().is_feynman_active_at_timestamp(block_timestamp) { + vec![ + (L1_BLOB_BASE_FEE_SLOT, U256::from(1000)), + (OVER_HEAD_SLOT, U256::from(1000)), + (SCALAR_SLOT, U256::from(1000)), + (L1_BLOB_BASE_FEE_SLOT, U256::from(10000)), + (COMMIT_SCALAR_SLOT, U256::from(1000)), + (BLOB_SCALAR_SLOT, U256::from(10000)), + (IS_CURIE_SLOT, U256::from(1)), + (PENALTY_THRESHOLD_SLOT, U256::from(2_000_000_000u64)), // penalty if <2x + (PENALTY_FACTOR_SLOT, U256::from(10_000_000_000u64)), // 10x penalty + (IS_FEYNMAN_SLOT, U256::from(1)), + ] + } else if strategy.spec().is_curie_active_at_block(block_number) { + vec![ + (L1_BLOB_BASE_FEE_SLOT, U256::from(1000)), + (OVER_HEAD_SLOT, U256::from(1000)), + (SCALAR_SLOT, U256::from(1000)), + (L1_BLOB_BASE_FEE_SLOT, U256::from(10000)), + (COMMIT_SCALAR_SLOT, U256::from(1000)), + (BLOB_SCALAR_SLOT, U256::from(10000)), + (IS_CURIE_SLOT, U256::from(1)), + ] + } else { + vec![ + (L1_BASE_FEE_SLOT, U256::from(1000)), + (OVER_HEAD_SLOT, U256::from(1000)), + (SCALAR_SLOT, U256::from(1000)), + ] + } + .into_iter() + .collect(); + + // load accounts in state + strategy.evm_mut().db_mut().insert_account_with_storage( + L1_GAS_PRICE_ORACLE_ADDRESS, + Default::default(), + l1_gas_oracle_storage, + ); + for add in block.senders() { + strategy + .evm_mut() + .db_mut() + .insert_account(*add, AccountInfo { balance: U256::MAX, ..Default::default() }); + } + + // execute and verify output + let sender = transaction.try_recover()?; + let tx = Recovered::new_unchecked(transaction, sender); + let res = strategy.execute_transaction(&tx); + + // check for error or execution outcome + let output = strategy.apply_post_execution_changes()?; + if let Some(error) = expected_error { + assert!(res.unwrap_err().to_string().contains(error)); + } else { + let BlockExecutionResult { receipts, .. } = output; + let gas_used = + MIN_TRANSACTION_GAS + if tx_type.is_eip7702() { PER_EMPTY_ACCOUNT_COST } else { 0 }; + let inner = alloy_consensus::Receipt { + cumulative_gas_used: gas_used, + status: true.into(), + ..Default::default() + }; + let into_scroll_receipt = |inner: alloy_consensus::Receipt| { + ScrollTransactionReceipt::new(inner, expected_l1_fee) + }; + let receipt = match tx_type { + ScrollTxType::Legacy => ScrollReceipt::Legacy(into_scroll_receipt(inner)), + ScrollTxType::Eip2930 => ScrollReceipt::Eip2930(into_scroll_receipt(inner)), + ScrollTxType::Eip1559 => ScrollReceipt::Eip1559(into_scroll_receipt(inner)), + ScrollTxType::Eip7702 => ScrollReceipt::Eip7702(into_scroll_receipt(inner)), + ScrollTxType::L1Message => ScrollReceipt::L1Message(inner), + }; + let expected = vec![receipt]; + + assert_eq!(receipts, expected); + } + + Ok(()) + } + + #[test] + fn test_apply_pre_execution_changes_curie_block() -> eyre::Result<()> { + // init curie transition block + let curie_block = block(CURIE_BLOCK_NUMBER - 1, 0, vec![]); + + // init strategy + let mut state = state(); + let mut strategy = executor(&curie_block, &mut state); + + // apply pre execution change + strategy.apply_pre_execution_changes()?; + + // take bundle + let state = strategy.evm_mut().db_mut(); + state.merge_transitions(BundleRetention::Reverts); + let bundle = state.take_bundle(); + + // assert oracle contract contains updated bytecode + let oracle = bundle.state.get(&L1_GAS_PRICE_ORACLE_ADDRESS).unwrap().clone(); + let oracle_bytecode = oracle.info.unwrap().code.unwrap(); + let bytecode = Bytecode::new_raw(CURIE_L1_GAS_PRICE_ORACLE_BYTECODE); + + // Note: Eq operator fails due to the presence of `table_ptr` in the `JumpTable` struct + // therefore we do a manual comparison. + assert_eq!( + bytecode.legacy_jump_table().unwrap().len(), + oracle_bytecode.legacy_jump_table().unwrap().len() + ); + assert_eq!( + bytecode.legacy_jump_table().unwrap().as_slice(), + oracle_bytecode.legacy_jump_table().unwrap().as_slice() + ); + assert_eq!(bytecode.bytecode(), oracle_bytecode.bytecode()); + + // check oracle contract contains storage changeset + let mut storage = oracle.storage.into_iter().collect::<Vec<(U256, StorageSlot)>>(); + storage.sort_by(|(a, _), (b, _)| a.cmp(b)); + for (got, expected) in storage.into_iter().zip(CURIE_L1_GAS_PRICE_ORACLE_STORAGE) { + assert_eq!(got.0, expected.0); + assert_eq!(got.1, StorageSlot { present_value: expected.1, ..Default::default() }); + } + + Ok(()) + } + + #[test] + fn test_apply_pre_execution_changes_not_curie_block() -> eyre::Result<()> { + // init block + let not_curie_block = block(NOT_CURIE_BLOCK_NUMBER, 0, vec![]); + + // init strategy + let mut state = state(); + let mut strategy = executor(&not_curie_block, &mut state); + + // apply pre execution change + strategy.apply_pre_execution_changes()?; + + // take bundle + let state = strategy.evm_mut().db_mut(); + state.merge_transitions(BundleRetention::Reverts); + let bundle = state.take_bundle(); + + // assert oracle contract is empty + let oracle = bundle.state.get(&L1_GAS_PRICE_ORACLE_ADDRESS); + assert!(oracle.is_none()); + + Ok(()) + } + + #[test] + fn test_execute_transactions_exceeds_block_gas_limit() -> eyre::Result<()> { + // prepare transaction exceeding block gas limit + let transaction = transaction(ScrollTxType::Legacy, BLOCK_GAS_LIMIT + 1); + let block = block(7096837, 0, vec![transaction.clone()]); + + // init strategy + let mut state = state(); + let mut strategy = executor(&block, &mut state); + + // execute and verify error + let sender = transaction.try_recover()?; + let tx = Recovered::new_unchecked(transaction, sender); + let res = strategy.execute_transaction(&tx); + assert_eq!( + res.unwrap_err().to_string(), + "transaction gas limit 10000001 is more than blocks available gas 10000000" + ); + + Ok(()) + } + + #[test] + fn test_execute_transactions_l1_message() -> eyre::Result<()> { + // Execute l1 message on curie block + let expected_l1_fee = U256::ZERO; + execute_transaction(ScrollTxType::L1Message, CURIE_BLOCK_NUMBER, 0, expected_l1_fee, None)?; + Ok(()) + } + + #[test] + fn test_execute_transaction_l1_message_feynman_fork() -> eyre::Result<()> { + // Execute L1 message on feynman block + let expected_l1_fee = U256::ZERO; + execute_transaction( + ScrollTxType::L1Message, + CURIE_BLOCK_NUMBER + 1, + FEYNMAN_BLOCK_TIMESTAMP, + expected_l1_fee, + None, + )?; + Ok(()) + } + + #[test] + fn test_execute_transactions_legacy_curie_fork() -> eyre::Result<()> { + // Execute legacy transaction on curie block + let expected_l1_fee = U256::from(10); + execute_transaction(ScrollTxType::Legacy, CURIE_BLOCK_NUMBER, 0, expected_l1_fee, None)?; + Ok(()) + } + + #[test] + fn test_execute_transactions_legacy_not_curie_fork() -> eyre::Result<()> { + // Execute legacy before curie block + let expected_l1_fee = U256::from(2); + execute_transaction( + ScrollTxType::Legacy, + NOT_CURIE_BLOCK_NUMBER, + 0, + expected_l1_fee, + None, + )?; + Ok(()) + } + + #[test] + fn test_execute_transactions_legacy_feynman_fork() -> eyre::Result<()> { + // Execute legacy transaction on feynman block + let expected_l1_fee = U256::from(10); + execute_transaction( + ScrollTxType::Legacy, + CURIE_BLOCK_NUMBER + 1, + FEYNMAN_BLOCK_TIMESTAMP, + expected_l1_fee, + None, + )?; + Ok(()) + } + + #[test] + fn test_execute_transactions_eip2930_curie_fork() -> eyre::Result<()> { + // Execute eip2930 transaction on curie block + let expected_l1_fee = U256::from(10); + execute_transaction(ScrollTxType::Eip2930, CURIE_BLOCK_NUMBER, 0, expected_l1_fee, None)?; + Ok(()) + } + + #[test] + fn test_execute_transactions_eip2930_not_curie_fork() -> eyre::Result<()> { + // Execute eip2930 transaction before curie block + execute_transaction( + ScrollTxType::Eip2930, + NOT_CURIE_BLOCK_NUMBER, + 0, + U256::ZERO, + Some("Eip2930 is not supported"), + )?; + Ok(()) + } + + #[test] + fn test_execute_transactions_eip2930_feynman_fork() -> eyre::Result<()> { + // Execute eip2930 transaction on feynman block + let expected_l1_fee = U256::from(10); + execute_transaction( + ScrollTxType::Eip2930, + CURIE_BLOCK_NUMBER + 1, + FEYNMAN_BLOCK_TIMESTAMP, + expected_l1_fee, + None, + )?; + Ok(()) + } + + #[test] + fn test_execute_transactions_eip1559_curie_fork() -> eyre::Result<()> { + // Execute eip1559 transaction on curie block + let expected_l1_fee = U256::from(10); + execute_transaction(ScrollTxType::Eip1559, CURIE_BLOCK_NUMBER, 0, expected_l1_fee, None)?; + Ok(()) + } + + #[test] + fn test_execute_transactions_eip1559_not_curie_fork() -> eyre::Result<()> { + // Execute eip1559 transaction before curie block + execute_transaction( + ScrollTxType::Eip1559, + NOT_CURIE_BLOCK_NUMBER, + 0, + U256::ZERO, + Some("Eip1559 is not supported"), + )?; + Ok(()) + } + + #[test] + fn test_execute_transaction_eip1559_feynman_fork() -> eyre::Result<()> { + // Execute eip1559 transaction on feynman block + let expected_l1_fee = U256::from(10); + execute_transaction( + ScrollTxType::Eip1559, + CURIE_BLOCK_NUMBER + 1, + FEYNMAN_BLOCK_TIMESTAMP, + expected_l1_fee, + None, + )?; + Ok(()) + } + + #[test] + fn test_execute_transactions_eip7702_euclid_v2_fork() -> eyre::Result<()> { + // Execute eip7702 transaction on euclid v2 block. + let expected_l1_fee = U256::from(19); + execute_transaction( + ScrollTxType::Eip7702, + EUCLID_V2_BLOCK_NUMBER, + EUCLID_V2_BLOCK_TIMESTAMP, + expected_l1_fee, + None, + )?; + Ok(()) + } + + #[test] + fn test_execute_transactions_eip7702_not_euclid_v2_fork() -> eyre::Result<()> { + // Execute eip7702 transaction before euclid v2 block + execute_transaction( + ScrollTxType::Eip7702, + EUCLID_V2_BLOCK_NUMBER - 1, + EUCLID_V2_BLOCK_TIMESTAMP - 1, + U256::ZERO, + Some("Eip7702 is not supported"), + )?; + Ok(()) + } + + #[test] + fn test_execute_transactions_eip7702_feynman_fork() -> eyre::Result<()> { + // Execute eip7702 transaction on feynman block + let expected_l1_fee = U256::from(19); + execute_transaction( + ScrollTxType::Eip7702, + CURIE_BLOCK_NUMBER + 1, + FEYNMAN_BLOCK_TIMESTAMP, + expected_l1_fee, + None, + )?; + Ok(()) + } + + #[test] + fn test_consistency_with_provided_compression_ratio() -> eyre::Result<()> { + let transactions = vec![ + transaction(ScrollTxType::Legacy, MIN_TRANSACTION_GAS), + transaction(ScrollTxType::Eip2930, MIN_TRANSACTION_GAS), + transaction(ScrollTxType::Eip1559, MIN_TRANSACTION_GAS), + transaction(ScrollTxType::Eip7702, MIN_TRANSACTION_GAS), + ]; + let compression_ratios = + transactions.iter().map(|tx| compute_compression_ratio(tx.input())).collect::<Vec<_>>(); + let with_compression_ratios = execute_block( + transactions.clone(), + CURIE_BLOCK_NUMBER + 1, + FEYNMAN_BLOCK_TIMESTAMP, + Some(compression_ratios), + )?; + let without_compression_ratios = + execute_block(transactions, CURIE_BLOCK_NUMBER + 1, FEYNMAN_BLOCK_TIMESTAMP, None)?; + assert_eq!(without_compression_ratios, with_compression_ratios); + Ok(()) + } +}
diff --git reth/crates/scroll/evm/src/l1.rs scroll-reth/crates/scroll/evm/src/l1.rs new file mode 100644 index 0000000000000000000000000000000000000000..2ad06a3839c666b2cb90f11b948401b472e179f5 --- /dev/null +++ scroll-reth/crates/scroll/evm/src/l1.rs @@ -0,0 +1,46 @@ +use super::spec_id_at_timestamp_and_number; +use reth_evm::block::BlockExecutionError; +use revm_primitives::U256; +use revm_scroll::l1block::L1BlockInfo; +use scroll_alloy_hardforks::ScrollHardforks; + +/// An extension trait for [`L1BlockInfo`] that allows us to calculate the L1 cost of a transaction +/// based off of the chain spec's activated hardfork. +pub trait RethL1BlockInfo { + /// Forwards an L1 transaction calculation to revm and returns the gas cost. + /// + /// ### Takes + /// - `chain_spec`: The chain spec for the node. + /// - `timestamp`: The timestamp of the current block. + /// - `block`: The block number of the current block. + /// - `input`: The calldata of the transaction. + /// - `is_l1_message`: Whether or not the transaction is a l1 message. + fn l1_tx_data_fee( + &mut self, + chain_spec: impl ScrollHardforks, + timestamp: u64, + block: u64, + input: &[u8], + compression_ratio: Option<U256>, + is_l1_message: bool, + ) -> Result<U256, BlockExecutionError>; +} + +impl RethL1BlockInfo for L1BlockInfo { + fn l1_tx_data_fee( + &mut self, + chain_spec: impl ScrollHardforks, + timestamp: u64, + block_number: u64, + input: &[u8], + compression_ratio: Option<U256>, + is_l1_message: bool, + ) -> Result<U256, BlockExecutionError> { + if is_l1_message { + return Ok(U256::ZERO); + } + + let spec_id = spec_id_at_timestamp_and_number(timestamp, block_number, chain_spec); + Ok(self.calculate_tx_l1_cost(input, spec_id, compression_ratio)) + } +}
diff --git reth/crates/scroll/evm/src/lib.rs scroll-reth/crates/scroll/evm/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..d8135610e1fa56dd4a1be50e8ca4c39329a72551 --- /dev/null +++ scroll-reth/crates/scroll/evm/src/lib.rs @@ -0,0 +1,163 @@ +//! Scroll evm execution implementation. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +mod build; + +mod config; + +mod execute; +pub use execute::{ScrollBlockExecutionInput, ScrollExecutorProvider}; + +mod l1; +pub use l1::RethL1BlockInfo; + +mod base_fee; +pub use base_fee::{ + ScrollBaseFeeProvider, DEFAULT_BASE_FEE_OVERHEAD, DEFAULT_BASE_FEE_SCALAR, + L1_BASE_FEE_PRECISION, L1_BASE_FEE_SLOT, MAX_L2_BASE_FEE, +}; + +mod receipt; +pub use receipt::ScrollRethReceiptBuilder; + +mod withdraw_root; +pub use withdraw_root::LoadWithdrawRoot; + +use crate::build::ScrollBlockAssembler; +use alloc::sync::Arc; + +use alloy_primitives::{Address, BlockNumber, BlockTimestamp}; +use reth_primitives_traits::NodePrimitives; +use reth_scroll_chainspec::ScrollChainSpec; +use reth_scroll_primitives::ScrollPrimitives; +use revm_scroll::ScrollSpecId; +pub use scroll_alloy_evm::{ + compute_compression_ratio, ScrollBlockExecutorFactory, ScrollDefaultPrecompilesFactory, + ScrollEvmFactory, ScrollTxCompressionRatios, +}; +pub use scroll_alloy_hardforks::{ScrollHardfork, ScrollHardforks}; + +/// Scroll EVM configuration. +#[derive(Debug)] +pub struct ScrollEvmConfig< + ChainSpec = ScrollChainSpec, + N: NodePrimitives = ScrollPrimitives, + R = ScrollRethReceiptBuilder, + P = ScrollDefaultPrecompilesFactory, +> { + /// Executor factory. + executor_factory: ScrollBlockExecutorFactory<R, Arc<ChainSpec>, P>, + /// Block assembler. + block_assembler: ScrollBlockAssembler<ChainSpec>, + /// Node primitives marker. + _pd: core::marker::PhantomData<N>, +} + +impl<ChainSpec: ScrollHardforks> ScrollEvmConfig<ChainSpec> { + /// Creates a new [`ScrollEvmConfig`] with the given chain spec for Scroll chains. + pub fn scroll(chain_spec: Arc<ChainSpec>) -> Self { + Self::new(chain_spec, ScrollRethReceiptBuilder::default()) + } +} + +impl<ChainSpec, N: NodePrimitives, R: Clone, P: Clone> Clone + for ScrollEvmConfig<ChainSpec, N, R, P> +{ + fn clone(&self) -> Self { + Self { + executor_factory: self.executor_factory.clone(), + block_assembler: self.block_assembler.clone(), + _pd: self._pd, + } + } +} + +impl<ChainSpec: ScrollHardforks, N: NodePrimitives, R, P: Default> + ScrollEvmConfig<ChainSpec, N, R, P> +{ + /// Creates a new [`ScrollEvmConfig`] with the given chain spec. + pub fn new(chain_spec: Arc<ChainSpec>, receipt_builder: R) -> Self { + Self { + block_assembler: ScrollBlockAssembler::new(chain_spec.clone()), + executor_factory: ScrollBlockExecutorFactory::new( + receipt_builder, + chain_spec, + ScrollEvmFactory::default(), + ), + _pd: core::marker::PhantomData, + } + } + + /// Returns the chain spec associated with this configuration. + pub const fn chain_spec(&self) -> &Arc<ChainSpec> { + self.executor_factory.spec() + } + + /// Returns the spec id at the given head. + pub fn spec_id_at_timestamp_and_number( + &self, + timestamp: BlockTimestamp, + number: BlockNumber, + ) -> ScrollSpecId { + let chain_spec = self.chain_spec(); + spec_id_at_timestamp_and_number(timestamp, number, chain_spec) + } +} + +/// Returns the spec id at the given timestamp and block number for the provided chain spec. +pub fn spec_id_at_timestamp_and_number( + timestamp: u64, + number: u64, + chain_spec: impl ScrollHardforks, +) -> ScrollSpecId { + if chain_spec + .scroll_fork_activation(ScrollHardfork::Feynman) + .active_at_timestamp_or_number(timestamp, number) + { + ScrollSpecId::FEYNMAN + } else if chain_spec + .scroll_fork_activation(ScrollHardfork::EuclidV2) + .active_at_timestamp_or_number(timestamp, number) + { + ScrollSpecId::EUCLID + } else if chain_spec + .scroll_fork_activation(ScrollHardfork::Euclid) + .active_at_timestamp_or_number(timestamp, number) || + chain_spec + .scroll_fork_activation(ScrollHardfork::DarwinV2) + .active_at_timestamp_or_number(timestamp, number) || + chain_spec + .scroll_fork_activation(ScrollHardfork::Darwin) + .active_at_timestamp_or_number(timestamp, number) + { + ScrollSpecId::DARWIN + } else if chain_spec + .scroll_fork_activation(ScrollHardfork::Curie) + .active_at_timestamp_or_number(timestamp, number) + { + ScrollSpecId::CURIE + } else if chain_spec + .scroll_fork_activation(ScrollHardfork::Bernoulli) + .active_at_timestamp_or_number(timestamp, number) + { + ScrollSpecId::BERNOULLI + } else { + ScrollSpecId::SHANGHAI + } +} + +/// The attributes for the next block env. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ScrollNextBlockEnvAttributes { + /// The timestamp of the next block. + pub timestamp: u64, + /// The suggested fee recipient for the next block. + pub suggested_fee_recipient: Address, + /// Block gas limit. + pub gas_limit: u64, + /// The base fee of the next block. + pub base_fee: u64, +}
diff --git reth/crates/scroll/evm/src/receipt.rs scroll-reth/crates/scroll/evm/src/receipt.rs new file mode 100644 index 0000000000000000000000000000000000000000..062ebc99e096d5c7df330f1ee1040769febf37f5 --- /dev/null +++ scroll-reth/crates/scroll/evm/src/receipt.rs @@ -0,0 +1,37 @@ +use alloy_consensus::{Eip658Value, Receipt}; +use alloy_evm::Evm; +use reth_scroll_primitives::{ScrollReceipt, ScrollTransactionSigned}; +use scroll_alloy_consensus::{ScrollTransactionReceipt, ScrollTxType}; +use scroll_alloy_evm::{ReceiptBuilderCtx, ScrollReceiptBuilder}; + +/// Basic builder for receipts of [`ScrollTransactionSigned`]. +#[derive(Debug, Default, Clone, Copy)] +#[non_exhaustive] +pub struct ScrollRethReceiptBuilder; + +impl ScrollReceiptBuilder for ScrollRethReceiptBuilder { + type Transaction = ScrollTransactionSigned; + type Receipt = ScrollReceipt; + + fn build_receipt<E: Evm>( + &self, + ctx: ReceiptBuilderCtx<'_, ScrollTransactionSigned, E>, + ) -> Self::Receipt { + let inner = Receipt { + // Success flag was added in `EIP-658: Embedding transaction status code in + // receipts`. + status: Eip658Value::Eip658(ctx.result.is_success()), + cumulative_gas_used: ctx.cumulative_gas_used, + logs: ctx.result.into_logs(), + }; + let into_scroll_receipt = |inner: Receipt| ScrollTransactionReceipt::new(inner, ctx.l1_fee); + + match ctx.tx.tx_type() { + ScrollTxType::Legacy => ScrollReceipt::Legacy(into_scroll_receipt(inner)), + ScrollTxType::Eip2930 => ScrollReceipt::Eip2930(into_scroll_receipt(inner)), + ScrollTxType::Eip1559 => ScrollReceipt::Eip1559(into_scroll_receipt(inner)), + ScrollTxType::Eip7702 => ScrollReceipt::Eip7702(into_scroll_receipt(inner)), + ScrollTxType::L1Message => ScrollReceipt::L1Message(inner), + } + } +}
diff --git reth/crates/scroll/evm/src/withdraw_root.rs scroll-reth/crates/scroll/evm/src/withdraw_root.rs new file mode 100644 index 0000000000000000000000000000000000000000..4775b889b55af2eb1f960eac6c8a73f4804acced --- /dev/null +++ scroll-reth/crates/scroll/evm/src/withdraw_root.rs @@ -0,0 +1,99 @@ +use alloy_primitives::{address, Address, U256}; +use revm::{database::State, Database}; + +const L2_MESSAGE_QUEUE_ADDRESS: Address = address!("0x5300000000000000000000000000000000000000"); +const WITHDRAW_TRIE_ROOT_SLOT: U256 = U256::ZERO; + +/// Instance that implements the trait can load the `L2MessageQueue` withdraw root in state. +pub trait LoadWithdrawRoot<DB: Database> { + /// Load the withdrawal root. + fn load_withdraw_root(&mut self) -> Result<(), DB::Error>; +} + +impl<DB: Database> LoadWithdrawRoot<DB> for State<DB> { + fn load_withdraw_root(&mut self) -> Result<(), DB::Error> { + // we load the account in cache and query the storage slot. The storage slot will only be + // loaded from database if it is not already know. + self.load_cache_account(L2_MESSAGE_QUEUE_ADDRESS)?; + let _ = revm::Database::storage(self, L2_MESSAGE_QUEUE_ADDRESS, WITHDRAW_TRIE_ROOT_SLOT); + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{collections::HashMap, convert::Infallible}; + + use alloy_primitives::B256; + use revm::{bytecode::Bytecode, state::AccountInfo}; + use revm_primitives::{StorageKey, StorageValue}; + + #[derive(Default)] + struct InMemoryDb { + pub accounts: HashMap<Address, AccountInfo>, + pub storage: HashMap<(Address, U256), U256>, + } + + impl Database for InMemoryDb { + type Error = Infallible; + + fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> { + Ok(self.accounts.get(&address).cloned()) + } + + fn code_by_hash(&mut self, _code_hash: B256) -> Result<Bytecode, Self::Error> { + Ok(Default::default()) + } + + fn storage( + &mut self, + address: Address, + index: StorageKey, + ) -> Result<StorageValue, Self::Error> { + Ok(self.storage.get(&(address, index)).copied().unwrap_or_default()) + } + + fn block_hash(&mut self, _number: u64) -> Result<B256, Self::Error> { + Ok(Default::default()) + } + } + + #[test] + fn test_should_load_withdraw_root() -> eyre::Result<()> { + // init db + let mut db = InMemoryDb::default(); + + // load L2 message queue contract + let withdraw_root = U256::random(); + db.accounts.insert(L2_MESSAGE_QUEUE_ADDRESS, Default::default()); + db.storage.insert((L2_MESSAGE_QUEUE_ADDRESS, U256::ZERO), withdraw_root); + + let mut state = + State::builder().with_database(db).with_bundle_update().without_state_clear().build(); + + assert!(state + .cache + .accounts + .get(&L2_MESSAGE_QUEUE_ADDRESS) + .map(|acc| acc.storage_slot(WITHDRAW_TRIE_ROOT_SLOT)) + .is_none()); + + // load root + state.load_withdraw_root()?; + + assert_eq!( + state + .cache + .accounts + .get(&L2_MESSAGE_QUEUE_ADDRESS) + .unwrap() + .storage_slot(WITHDRAW_TRIE_ROOT_SLOT) + .unwrap(), + withdraw_root + ); + + Ok(()) + } +}
diff --git reth/crates/scroll/hardforks/Cargo.toml scroll-reth/crates/scroll/hardforks/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..bd69a2a489a6663f42d2a38975dba913856252f5 --- /dev/null +++ scroll-reth/crates/scroll/hardforks/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "reth-scroll-forks" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "Scroll hardforks used in reth" + +[lints] +workspace = true + +[dependencies] +# reth +reth-ethereum-forks.workspace = true + +# ethereum +scroll-alloy-hardforks.workspace = true +alloy-chains.workspace = true +alloy-primitives.workspace = true + +# io +serde = { workspace = true, optional = true } + +# misc +auto_impl.workspace = true +once_cell.workspace = true + +[features] +default = ["std"] +std = [ + "alloy-primitives/std", + "once_cell/std", + "serde?/std", + "alloy-chains/std", + "reth-ethereum-forks/std", + "scroll-alloy-hardforks/std", +] +serde = [ + "dep:serde", + "alloy-chains/serde", + "alloy-primitives/serde", + "reth-ethereum-forks/serde", + "scroll-alloy-hardforks/serde", +]
diff --git reth/crates/scroll/hardforks/docs/hardforks.md scroll-reth/crates/scroll/hardforks/docs/hardforks.md new file mode 100644 index 0000000000000000000000000000000000000000..8f00794c94799714b14719be3292d93e159bbd53 --- /dev/null +++ scroll-reth/crates/scroll/hardforks/docs/hardforks.md @@ -0,0 +1,433 @@ +--- +section: technology +date: Last Modified +title: "Scroll Upgrades" +lang: "en" +permalink: "technology/overview/scroll-upgrades" +--- + +As the team continues to progress on Scroll's roadmap, we will be upgrading the Scroll network to include new features +and improvements. + +The following contracts are used to initiate upgrades and execute upgrades after the two-week timelock period: + +| Contract | Network | Address | +|--------------------|----------|---------------------------------------------------------------------------------------------------------------------------| +| L1 Scroll Multisig | Ethereum | [`0xEfc9D1096fb65c832207E5e7F13C2D1102244dbe`](https://etherscan.io/address/0xEfc9D1096fb65c832207E5e7F13C2D1102244dbe) | +| L1 Timelock | Ethereum | [`0x1A658B88fD0a3c82fa1a0609fCDbD32e7dd4aB9C`](https://etherscan.io/address/0x1A658B88fD0a3c82fa1a0609fCDbD32e7dd4aB9C) | +| L2 Scroll Multisig | Scroll | [`0xEfc9D1096fb65c832207E5e7F13C2D1102244dbe`](https://scrollscan.com/address/0xEfc9D1096fb65c832207E5e7F13C2D1102244dbe) | +| L2 Timelock | Scroll | [`0xf6069DB81239E5194bb53f83aF564d282357bc99`](https://scrollscan.com/address/0xf6069DB81239E5194bb53f83aF564d282357bc99) | + +You can join our [Telegram channel for technical updates](https://t.me/scroll_tech_updates), which includes future +upgrade announcements and on-chain operation events. + +## `DarwinV2` Upgrade + +### Overview + +During internal testing, we identified that blocks may not always be compressible under certain conditions, which leads +to unprovable chunks and batches. +To fix this issue, a minor upgrade has been conducted so that uncompressed blobs will be enabled when this special case +is detected. + +### Timeline + +As this is a security related patch, we bypassed the 7-day timelock mechanism. + +- **Scroll Sepolia**: August 28th, 2024 +- **Scroll Mainnet**: September 2nd, 2024 + +### Compatibility + +#### Sequencer and Follower Nodes (l2geth) + +The new node version is `v5.7.0`. See +the [release notes](https://github.com/scroll-tech/go-ethereum/releases/tag/scroll-v5.7.0) for more information. + +This upgrade does not change Scroll's state transition function, so it is backward compatible. However, the format of +the batch data committed to Ethereum changes. As a result, nodes that enabled rollup verification (`--rollup.verify`) +must upgrade to be able to follow the chain. + +#### Dapps and Indexers + +A change has been implemented to Scroll Mainnet to enhance sequencer throughput, which adjusted the maximum reorg depth +to 17 blocks. Previously, the system performed thorough capacity checks within the signer thread to determine whether +transactions exceed the circuit limit. While this ensures that all transactions within a block are compliant, it also +requires additional CPU resources. +We introduced a new circuit capacity checking scheme on Mainnet. The sequencer thread now will continue to perform +capacity checks, but in a more approximate manner. In parallel, 16 worker threads will accurately verify the capacity of +previous blocks. As a result, a reorg could occur with a maximum depth of 17 blocks, although the likelihood of this is +low. + +For indexers, the `BatchHeader` version has been upgraded to 4. This is backward compatible (the only exception is for +developers decoding the blob payload, which has changed slightly). + +## Darwin Upgrade + +### Overview + +This upgrade will reduce gas fees by 34% by using a single aggregated proof for multiple batches, eliminating the need +to finalize each batch individually. + +- Darwin uses a new [V3 batch codec](https://github.com/scroll-tech/da-codec/tree/main/encoding/codecv3). +- In addition to the previous notions of `chunk` and `batch`, we have introduced a new concept called `bundle`. + - `Chunk`: A unit of zkEVM proving, consisting of a list of L2 blocks. + - `Batch`: A collection of chunks encoded into one EIP-4844 blob, serving as the unit of Data Availability. + - `Bundle`: A series of batches that functions as the unit of finalization. + + The main difference compared to Curie is that Scroll will now finalize multiple batches using a single aggregated + bundle proof. + +- The on-chain bundle proof verifier uses a new public input layout. + +### Timeline + +- **Scroll Sepolia** + - Network Upgrade: August 14th, 2024 +- **Scroll Mainnet** + - Upgrade Initiation: August 5th, 2024 + - Timelock Completion & Upgrade: August 21st, 2024 + +### Technical Details + +#### Contract Changes + +*Note: Since the previous Curie upgrade, we have migrated the Scroll contracts to a new repo +at [scroll-contracts](https://github.com/scroll-tech/scroll-contracts).* + +The code changes for this upgrade are implemented in [this PR](https://github.com/scroll-tech/scroll-contracts/pull/4). +The key changes are as follows: + +- We have introduced a new `BatchHeaderV3Codec`. +- We have changed how messages are processed in the `L1MessageQueue` contract. Prior to Darwin, we would process + messages when a batch is finalized. After Darwin, most of this processing is moved to the commit step. +- We have introduced a new public input format for bundle proofs. This is implemented in a new contract + `IZkEvmVerifierV2`, which is in turn added to `MultipleVersionRollupVerifier`. +- In the `ScrollChain` contract `version=3` batches will now be committed through a new function called + `commitBatchWithBlobProof`. Bundles will be finalized using a new function called `finalizeBundleWithProof`. + +See the contract [release notes](https://github.com/scroll-tech/scroll-contracts/releases/tag/v1.0.0) for more +information. + +#### Node Changes + +The new node version is `v5.6.0`. See +the [release notes](https://github.com/scroll-tech/go-ethereum/releases/tag/scroll-v5.6.0) for more information. + +The main changes are: + +- Implementation of timestamp-based hard forks. +- Processing V3 batch codec in rollup-verifier. + +#### zkEVM circuit changes + +The new version of zkevm circuits is `v0.12.0`. +See [here](https://github.com/scroll-tech/zkevm-circuits/releases/tag/v0.12.0) for the release log. + +We have introduced a `RecursionCircuit` that will bundle multiple sequential batches by recursively aggregating the +SNARKs from the `BatchCircuit` (previously `AggregationCircuit`). The previously 5 layer proving system is now 7 layers +as we introduce: + +- 6th Layer (layer5): `RecursionCircuit` that recursively aggregates `BatchCircuit` SNARKs. +- 7th Layer (layer6): `CompressionCircuit` that compresses the `RecursionCircuit` SNARK and produce an EVM-verifiable + validity proof. + +The public input to the `BatchCircuit` is now context-aware of the “previous” `batch`, which allows us to implement the +recursion scheme we adopted ( +described [here](https://scrollzkp.notion.site/Upgrade-4-Darwin-Documentation-05a3ecb59e9d4f288254701f8c888173) +in-depth). + +#### Audits + +- TrailofBits: coming soon! + +### Compatibility + +#### Sequencer and Follower Nodes (l2geth) + +This upgrade does not alter the state transition function and is therefore backward-compatible. However, we strongly +recommend node operators to upgrade to [v5.6.0](https://github.com/scroll-tech/go-ethereum/releases/tag/scroll-v5.6.0). + +#### Dapps and Indexers + +There are some major changes to how we commit and finalize batches after Darwin. + +- Batches will be encoded using the + new [V3 batch codec](https://github.com/scroll-tech/da-codec/tree/main/encoding/codecv3). This version adds two new + fields: + 1. `lastBlockTimestamp` (the timestamp of the last block in this batch). + 2. `blobDataProof` (the KZG challenge point evaluation proof). + + This version removes `skippedL1MessageBitmap`. There will be no changes to how the blob data is encoded and + compressed. +- Batches will be committed using the `commitBatchWithBlobProof` function (instead of the previous `commitBatch`). + + New function signature: + + ```solidity + function commitBatchWithBlobProof(uint8 _version, bytes calldata _parentBatchHeader, bytes[] memory _chunks, bytes calldata _skippedL1MessageBitmap, bytes calldata _blobDataProof) + ``` + +- Batches will be finalized using the `finalizeBundleWithProof` function (instead of the previous + `finalizeBatchWithProof4844`). + + New function signature: + + ```solidity + function finalizeBundleWithProof(bytes calldata _batchHeader, bytes32 _postStateRoot, bytes32 _withdrawRoot, bytes calldata _aggrProof) + ``` + +- The semantics of the `FinalizeBatch` event will change: It will now mean that all batches between the last finalized + batch and the event’s `_batchIndex` have been finalized. The event’s stateRoot and withdrawRoot values belong to the + last finalized batch in the bundle. Finalized roots for intermediate batches are no longer available. + + The semantics of the `CommitBatch` and `RevertBatch` events will not change. + +Recommendations: + +- Indexers that decode committed batch data should be adjusted to use the new codec and the new function signature. +- Indexers that track batch finalization status should be adjusted to consider the new event semantics. + +## Curie Upgrade + +### Overview + +This significant upgrade will reduce gas fees on the Scroll chain by 1.5x. Highlights include: + +- Compresses the data stored in blobs using the [zstd](https://github.com/scroll-tech/da-codec/tree/main/libzstd) + algorithm. This compression reduces the data size, allowing each blob to store more transactions, thereby reducing + data availability cost per transaction. +- Adopts a modified version of the EIP-1559 pricing model which is compatible with the EIP-1559 transaction interface, + bringing beneftis such as more accurate transaction pricing and a more predictable and stable fee structure. +- Support for new EVM opcodes `TLOAD`, `TSTORE`, and `MCOPY`. Users can safely use the latest Solidity compiler version + `0.8.26` to build the contracts. +- Introduces a dynamic block time. During periods of traffic congestion, a block will be packed when the number of + transactions reaches the circuit limit instead of waiting for the 3-second interval. + +### Timeline + +- **Scroll Sepolia** + - Network Upgrade: June 17th, 2024 +- **Scroll Mainnet** + - Upgrade Initiation: June 20th, 2024 + - Timelock Completion & Upgrade: July 3rd, 2024 + +### Technical Details + +#### Contract Changes + +The code changes for this upgrade are documented in the following PRs: + +- [Accept compressed batches](https://github.com/scroll-tech/scroll/pull/1317) +- [Update `L1GasPriceOracle`](https://github.com/scroll-tech/scroll/pull/1343) +- [Change `MAX_COMMIT_SCALAR` and `MAX_BLOB_SCALAR` to 1e18](https://github.com/scroll-tech/scroll/pull/1354) +- [Remove batch index check when updating a verifier](https://github.com/scroll-tech/scroll/pull/1372) + +The main changes are as follows: + +- The rollup contract (`ScrollChain`) will now accept batches with both versions 1 and + 2. [Version 1](https://github.com/scroll-tech/da-codec/tree/main/encoding/codecv1) is used for uncompressed blobs ( + pre-Curie), while [version 2](https://github.com/scroll-tech/da-codec/tree/main/encoding/codecv2) is used for + compressed blobs (post-Curie). +- The `L1GasPriceOracle` contract will be updated to change the data fee formula to account for blob DA, providing a + more accurate estimation of DA costs: + - Original formula: `(l1GasUsed(txRlp) + overhead) * l1BaseFee * scalar` + - New formula: `l1BaseFee * commitScalar + len(txRlp) * l1BlobBaseFee * blobScalar` + +#### Node Changes + +The new node version is `v5.5.0`. See +the [release notes](https://github.com/scroll-tech/go-ethereum/releases/tag/scroll-v5.5.0) for the list of changes. + +#### zkEVM circuit changes + +The new version of zkevm circuits is `v0.11.4`. +See [here](https://github.com/scroll-tech/zkevm-circuits/releases/tag/v0.11.4) for the release log. + +#### Audits + +- TrailofBits: coming soon! +- [Zellic](https://github.com/Zellic/publications/blob/master/Scroll%20zkEVM%20-%20Zellic%20Audit%20Report.pdf) + +### Compatibility + +#### Sequencer and Follower Nodes (l2geth) + +This upgrade is a hard fork, introducing the `TLOAD`, `TSTORE`, and `MCOPY` opcodes. Operators running an `l2geth` node +are required to upgrade before the hard fork block. For more information, see +the [node release note](https://github.com/scroll-tech/go-ethereum/releases/tag/scroll-v5.4.2). + +#### Dapps and Indexers + +For dApps, this upgrade is backward compatible. Developers should adjust the gas fee settings to incorporate the +EIP-1559 pricing model. Note that dApps can no longer rely on the fixed 3-second block time in the application logic. + +For indexers, the [data format](https://docs.scroll.io/en/technology/chain/rollup/#codec) remains the same. The will be +however changes to the data content: + +- The `version` field in `BatchHeader` will be changed to 2 since Curie block. +- The data stored in blob will be compressed and can be decompressed + by [zstd v1.5.6](https://github.com/facebook/zstd/releases/tag/v1.5.6). + +## Bernoulli Upgrade + +### Overview + +This upgrade features a significant reduction in transaction costs by introducing support for EIP-4844 data blobs and +supporting the SHA2-256 precompile. + +### Timeline + +- **Scroll Sepolia** + - Network Upgrade: April 15th, 2024 +- **Scroll Mainnet** + - Upgrade Initiation: April 15th, 2024 + - Timelock Completion & Upgrade: April 29th, 2024 + +### Technical Details + +#### Contract changes + +The contract changes for this upgrade are in [this PR](https://github.com/scroll-tech/scroll/pull/1179), along with the +audit +fixes [here](https://github.com/scroll-tech/scroll/pulls?q=is%3Apr+created%3A2024-04-10..2024-04-11+fix+in%3Atitle+label%3Abug). +The main changes are as follows: + +- `ScrollChain` now accepts batches with either calldata or blob encoding in `commitBatch`. +- `ScrollChain` now supports finalizing blob-encoded batches through `finalizeBatchWithProof4844`. +- `MultipleVersionRollupVerifier` can now manage different on-chain verifiers for each batch encoding version. + +#### Node changes + +The new node version is `v5.3.0`. See [here](https://github.com/scroll-tech/go-ethereum/releases/tag/scroll-v5.3.0) for +the release log. + +#### zkEVM circuit changes + +The new version of zkevm circuits is `v0.10.3`. +See [here](https://github.com/scroll-tech/zkevm-circuits/releases/tag/v0.10.3) for the release log. + +#### Audits + +- [OpenZeppelin](https://blog.openzeppelin.com/scroll-eip-4844-support-audit) +- [TrailofBits](https://github.com/trailofbits/publications/blob/master/reviews/2024-04-scroll-4844-blob-securityreview.pdf) + +### Compatibility + +#### Sequencer and follower nodes (l2geth) + +This upgrade is a hard fork as it introduces the new blob data type and the SHA2-256 precompiled contract. Operators +running an `l2geth` node are required to upgrade before the hard fork block. See +the [node releases](https://github.com/scroll-tech/go-ethereum/releases) for more information. + +#### Indexers and Bridges + +This upgrade changes the format that Scroll uses to publish data to Ethereum. Projects that rely on this data should +carefully review [the new data format](/en/technology/chain/rollup/#codec), and check whether their decoders need to be +adjusted. A summary of the new format: + +- The format of [ + `BlockContext`](https://github.com/scroll-tech/scroll/blob/5362e28f744093495c1c09a6b68fc96a3264278b/common/types/encoding/codecv1/codecv1.go#L125) + will not change. +- `Chunks` + will [no longer include](https://github.com/scroll-tech/scroll/blob/5362e28f744093495c1c09a6b68fc96a3264278b/common/types/encoding/codecv1/codecv1.go#L162) + the L2 transaction data. This will instead + be [stored in a blob](https://github.com/scroll-tech/scroll/blob/5362e28f744093495c1c09a6b68fc96a3264278b/common/types/encoding/codecv1/codecv1.go#L284) + attached to the `commitBatch` transaction. +- `BatchHeader` now contains one new field, [ + `BlobVersionedHash`](https://github.com/scroll-tech/scroll/blob/5362e28f744093495c1c09a6b68fc96a3264278b/common/types/encoding/codecv1/codecv1.go#L405). + +#### Provers + +This upgrade involves a breaking change in [zkevm-circuits](https://github.com/scroll-tech/zkevm-circuits). Operators +running a prover node are required to upgrade. + +## Bridge Upgrade + +### Overview + +To reduce bridging costs, we implemented several gas optimizations on our bridge and rollup contract suite. The +optimization techniques used include the following: + +- We will now use constants to store some companion contract addresses, instead of using storage variables. This is + possible since these values should (almost) never change. With this change we can save on a few storage load + operations. +- We updated the intrinsic gas estimation in `L1MessageQueue` to use a simple upper bound instead of an exact + calculation. The two results will be similar for most bridge transactions but the new implementation is significantly + cheaper. +- We merged two contracts `L1MessageQueue` and `L2GasPriceOracle` to save on call costs from one contract to the other. + +### Timeline + +- **Scroll Sepolia:** + - Network Upgrade: January 19, 2024 +- **Scroll Mainnet:** + - Upgrade Initiation: February 7, 2024 + - Timelock Completion & Upgrade: February 21, 2024 + +### Technical Details + +#### Code Changes + +- [Bridge Cost Optimization](https://github.com/scroll-tech/scroll/pull/1011) +- [Audit Fixes](https://github.com/scroll-tech/scroll/pulls?q=OZ+is%3Apr+created%3A2024-01-27..2024-02-10) +- [Previously deployed version](https://github.com/scroll-tech/scroll/tree/ff380141a8cbcc214dc65f17ffa44faf4be646b6) ( + commit `ff380141a8cbcc214dc65f17ffa44faf4be646b6`) +- [Version deployed](https://github.com/scroll-tech/scroll/tree/6030927680a92d0285c2c13e6bb27ed27d1f32d1) (commit + `6030927680a92d0285c2c13e6bb27ed27d1f32d1`) + +#### Audits + +- [OpenZeppelin](https://blog.openzeppelin.com/scroll-bridge-gas-optimizations-audit) + +#### List of Changes + +**Changes to L1 contracts:** + +- In `ScrollChain`, change `messageQueue` and `verifier` to `immutable`. +- In `L1ScrollMessenger`, change `counterpart`, `rollup`, and `messageQueue` to `immutable`. +- In all token gateways, change `counterpart`, `router`, and `messenger` to `immutable`. +- Merge `L1MessageQueue` and `L2GasPriceOracle` into a single contract `L1MessageQueueWithGasPriceOracle` (deployed on + the same address as the previous `L1MessageQueue`). In this contract, we also change `messenger` and `scrollChain` to + `immutable`, and simplify `calculateIntrinsicGasFee`. + +**Changes to L2 contracts:** + +- In `L2ScrollMessenger`, change `counterpart` to `immutable`. +- In all token gateways, change `counterpart`, `router`, and `messenger` to `immutable`. + +**Contracts affected:** + +- **L1:** `L1MessageQueue`, `L2GasPriceOracle`, `ScrollChain`, `L1WETHGateway`, `L1StandardERC20Gateway`, + `L1GatewayRouter`, `L1ScrollMessenger`, `L1CustomERC20Gateway`, `L1ERC721Gateway`, `L1ERC1155Gateway`. +- **L2:** `L2ScrollMessenger`, `L2WETHGateway`, `L2StandardERC20Gateway`, `L2GatewayRouter`, `L2CustomERC20Gateway`, + `L2ERC721Gateway`, `L2ERC1155Gateway`. + +#### Compatibility + +##### Sequencer and follower nodes (l2geth) + +Operators running an `l2geth` node do not need to upgrade. The changes in this upgrade will not affect `l2geth`. + +##### Dapps and indexers + +Dapps and indexers (and similar off-chain infrastructure) that query contracts or rely on contract interfaces would, in +most cases, not need to be changed. The majority of the contract changes are internal and/or backward compatible. + +If your application depends on [ +`L2GasPriceOracle`](https://etherscan.io/address/0x987e300fDfb06093859358522a79098848C33852) to monitor how Scroll keeps +track of the L2 gas price on L1, from the upgrade block number you will need to start monitoring [ +`L1MessageQueueWithGasPriceOracle`](https://etherscan.io/address/0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B). + +The original gas price oracle contract will be deprecated: it will no longer be updated or used by the Scroll bridge. + +- Ethereum: + - `L2GasPriceOracle`: [ + `0x987e300fDfb06093859358522a79098848C33852`](https://etherscan.io/address/0x987e300fDfb06093859358522a79098848C33852) + - `L1MessageQueueWithGasPriceOracle`: [ + `0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B`](https://etherscan.io/address/0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B) +- Sepolia: + - `L2GasPriceOracle`: [ + `0x247969F4fad93a33d4826046bc3eAE0D36BdE548`](https://sepolia.etherscan.io/address/0x247969F4fad93a33d4826046bc3eAE0D36BdE548) + - `L1MessageQueueWithGasPriceOracle`: [ + `0xF0B2293F5D834eAe920c6974D50957A1732de763`](https://sepolia.etherscan.io/address/0xF0B2293F5D834eAe920c6974D50957A1732de763) \ No newline at end of file
diff --git reth/crates/scroll/hardforks/src/lib.rs scroll-reth/crates/scroll/hardforks/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..c780f15eb3f219ded630733b50f224ad383d7708 --- /dev/null +++ scroll-reth/crates/scroll/hardforks/src/lib.rs @@ -0,0 +1,92 @@ +//! Scroll-Reth hard forks. + +#![cfg_attr(not(feature = "std"), no_std)] +#![doc = include_str!("../docs/hardforks.md")] +#[cfg(not(feature = "std"))] +extern crate alloc as std; + +use reth_ethereum_forks::{ChainHardforks, EthereumHardfork, ForkCondition, Hardfork}; + +// Re-export scroll-alloy-hardforks types. +pub use scroll_alloy_hardforks::{ScrollHardfork, ScrollHardforks}; + +#[cfg(not(feature = "std"))] +use once_cell::sync::Lazy as LazyLock; +#[cfg(feature = "std")] +use std::sync::LazyLock; +use std::vec; + +/// Scroll mainnet hardforks +pub static SCROLL_MAINNET_HARDFORKS: LazyLock<ChainHardforks> = LazyLock::new(|| { + ChainHardforks::new(vec![ + (EthereumHardfork::Homestead.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Dao.boxed(), ForkCondition::Never), + (EthereumHardfork::Tangerine.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::SpuriousDragon.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Byzantium.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Constantinople.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Petersburg.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Istanbul.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::MuirGlacier.boxed(), ForkCondition::Never), + (EthereumHardfork::Berlin.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::London.boxed(), ForkCondition::Never), + (EthereumHardfork::ArrowGlacier.boxed(), ForkCondition::Never), + (ScrollHardfork::Archimedes.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Shanghai.boxed(), ForkCondition::Block(0)), + (ScrollHardfork::Bernoulli.boxed(), ForkCondition::Block(5220340)), + (ScrollHardfork::Curie.boxed(), ForkCondition::Block(7096836)), + (ScrollHardfork::Darwin.boxed(), ForkCondition::Timestamp(1724227200)), + (ScrollHardfork::DarwinV2.boxed(), ForkCondition::Timestamp(1725264000)), + (ScrollHardfork::Euclid.boxed(), ForkCondition::Timestamp(1744815600)), + (ScrollHardfork::EuclidV2.boxed(), ForkCondition::Timestamp(1745305200)), + (ScrollHardfork::Feynman.boxed(), ForkCondition::Timestamp(1755576000)), + ]) +}); + +/// Scroll sepolia hardforks +pub static SCROLL_SEPOLIA_HARDFORKS: LazyLock<ChainHardforks> = LazyLock::new(|| { + ChainHardforks::new(vec![ + (EthereumHardfork::Homestead.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Tangerine.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::SpuriousDragon.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Byzantium.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Constantinople.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Petersburg.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Istanbul.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Berlin.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::London.boxed(), ForkCondition::Block(0)), + (ScrollHardfork::Archimedes.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Shanghai.boxed(), ForkCondition::Block(0)), + (ScrollHardfork::Bernoulli.boxed(), ForkCondition::Block(3747132)), + (ScrollHardfork::Curie.boxed(), ForkCondition::Block(4740239)), + (ScrollHardfork::Darwin.boxed(), ForkCondition::Timestamp(1723622400)), + (ScrollHardfork::DarwinV2.boxed(), ForkCondition::Timestamp(1724832000)), + (ScrollHardfork::Euclid.boxed(), ForkCondition::Timestamp(1741680000)), + (ScrollHardfork::EuclidV2.boxed(), ForkCondition::Timestamp(1741852800)), + (ScrollHardfork::Feynman.boxed(), ForkCondition::Timestamp(1753167600)), + ]) +}); + +/// Dev hardforks +pub static DEV_HARDFORKS: LazyLock<ChainHardforks> = LazyLock::new(|| { + ChainHardforks::new(vec![ + (EthereumHardfork::Homestead.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Tangerine.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::SpuriousDragon.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Byzantium.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Constantinople.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Petersburg.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Istanbul.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Berlin.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::London.boxed(), ForkCondition::Block(0)), + (ScrollHardfork::Archimedes.boxed(), ForkCondition::Block(0)), + (EthereumHardfork::Shanghai.boxed(), ForkCondition::Timestamp(0)), + (ScrollHardfork::Bernoulli.boxed(), ForkCondition::Block(0)), + (ScrollHardfork::Curie.boxed(), ForkCondition::Block(0)), + (ScrollHardfork::Darwin.boxed(), ForkCondition::Timestamp(0)), + (ScrollHardfork::DarwinV2.boxed(), ForkCondition::Timestamp(0)), + (ScrollHardfork::Euclid.boxed(), ForkCondition::Timestamp(0)), + (ScrollHardfork::EuclidV2.boxed(), ForkCondition::Timestamp(0)), + (ScrollHardfork::Feynman.boxed(), ForkCondition::Timestamp(0)), + ]) +});
diff --git reth/crates/scroll/node/Cargo.toml scroll-reth/crates/scroll/node/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..01c5794bb5c02e5f90f6b2488e04c131d71dd1fe --- /dev/null +++ scroll-reth/crates/scroll/node/Cargo.toml @@ -0,0 +1,118 @@ +[package] +name = "reth-scroll-node" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# reth +reth-chainspec.workspace = true +reth-db = { workspace = true, features = ["scroll-alloy-traits"] } +reth-engine-local = { workspace = true, features = ["scroll-alloy-traits"] } +reth-eth-wire-types.workspace = true +reth-evm = { workspace = true, features = ["scroll-alloy-traits"] } +reth-e2e-test-utils = { workspace = true, optional = true } +reth-network.workspace = true +reth-node-api.workspace = true +reth-node-core = { workspace = true, optional = true } +reth-node-types.workspace = true +reth-node-builder.workspace = true +reth-payload-builder.workspace = true +reth-primitives = { workspace = true, features = ["c-kzg"] } +reth-primitives-traits.workspace = true +reth-provider.workspace = true +reth-rpc-eth-types.workspace = true +reth-rpc-server-types = { workspace = true, optional = true } +reth-tasks = { workspace = true, optional = true } +reth-tracing.workspace = true +reth-transaction-pool.workspace = true +reth-trie-db.workspace = true + +# revm +revm = { workspace = true, features = ["c-kzg"] } + +# alloy +alloy-consensus.workspace = true +alloy-genesis = { workspace = true, optional = true } +alloy-primitives.workspace = true +alloy-rpc-types-engine.workspace = true +alloy-rpc-types-eth.workspace = true + +# scroll-reth +reth-scroll-chainspec.workspace = true +reth-scroll-consensus.workspace = true +reth-scroll-engine-primitives.workspace = true +reth-scroll-evm.workspace = true +reth-scroll-payload.workspace = true +reth-scroll-primitives = { workspace = true, features = ["serde", "serde-bincode-compat", "reth-codec"] } +reth-scroll-rpc.workspace = true +reth-scroll-txpool.workspace = true + +# scroll-alloy +scroll-alloy-consensus.workspace = true +scroll-alloy-evm.workspace = true +scroll-alloy-hardforks.workspace = true +scroll-alloy-network.workspace = true +scroll-alloy-rpc-types-engine.workspace = true + +# misc +clap.workspace = true +eyre.workspace = true +serde_json = { workspace = true, optional = true } +tracing.workspace = true +tokio.workspace = true + +[dev-dependencies] +reth-scroll-node = { workspace = true, features = ["test-utils"] } +reth-db.workspace = true +reth-node-core.workspace = true +reth-node-builder = { workspace = true, features = ["test-utils"] } +reth-provider = { workspace = true, features = ["test-utils"] } +reth-revm = { workspace = true, features = ["test-utils"] } +reth-tasks.workspace = true + +alloy-primitives.workspace = true +scroll-alloy-consensus.workspace = true +alloy-consensus.workspace = true + +[features] +default = ["reth-codec", "scroll-alloy-traits"] +reth-codec = [ + "reth-scroll-primitives/reth-codec", +] +test-utils = [ + "dep:alloy-genesis", + "dep:reth-e2e-test-utils", + "dep:reth-node-core", + "dep:reth-rpc-server-types", + "dep:reth-tasks", + "dep:serde_json", + "reth-chainspec/test-utils", + "reth-evm/test-utils", + "reth-network/test-utils", + "reth-node-builder/test-utils", + "reth-payload-builder/test-utils", + "reth-primitives/test-utils", + "reth-primitives-traits/test-utils", + "reth-provider/test-utils", + "reth-scroll-payload/test-utils", + "reth-transaction-pool/test-utils", + "reth-trie-db/test-utils", + "reth-db/test-utils", + "reth-revm/test-utils", + "reth-scroll-node/test-utils", +] +scroll-alloy-traits = [ + "reth-db/scroll-alloy-traits", + "reth-evm/scroll-alloy-traits", + "reth-primitives-traits/scroll-alloy-traits", + "reth-scroll-node/scroll-alloy-traits", + "reth-engine-local/scroll-alloy-traits", +]
diff --git reth/crates/scroll/node/src/addons.rs scroll-reth/crates/scroll/node/src/addons.rs new file mode 100644 index 0000000000000000000000000000000000000000..9b2bce66377fc61bcb5f1d073c8337d4e7cfab97 --- /dev/null +++ scroll-reth/crates/scroll/node/src/addons.rs @@ -0,0 +1,251 @@ +use crate::{ + builder::{engine::ScrollEngineValidatorBuilder, payload::SCROLL_DEFAULT_PAYLOAD_SIZE_LIMIT}, + ScrollStorage, +}; +use reth_evm::{ConfigureEngineEvm, EvmFactory, EvmFactoryFor}; +use reth_node_api::{AddOnsContext, NodeAddOns, PayloadTypes}; +use reth_node_builder::{ + rpc::{ + BasicEngineApiBuilder, BasicEngineValidatorBuilder, EngineValidatorAddOn, EthApiBuilder, + Identity, RethRpcAddOns, RethRpcMiddleware, RpcAddOns, RpcHandle, + }, + FullNodeComponents, +}; +use reth_node_types::NodeTypes; +use reth_rpc_eth_types::error::FromEvmError; +use reth_scroll_chainspec::ScrollChainSpec; +use reth_scroll_engine_primitives::ScrollEngineTypes; +use reth_scroll_evm::ScrollNextBlockEnvAttributes; +use reth_scroll_primitives::ScrollPrimitives; +use reth_scroll_rpc::{ + eth::{ScrollEthApiBuilder, DEFAULT_MIN_SUGGESTED_PRIORITY_FEE}, + ScrollEthApiError, +}; +use revm::context::TxEnv; +use scroll_alloy_evm::ScrollTransactionIntoTxEnv; +use scroll_alloy_hardforks::ScrollHardforks; +use scroll_alloy_network::Scroll; +use std::marker::PhantomData; + +/// Marker trait for Scroll node types with standard engine, chain spec, and primitives. +pub trait ScrollNodeTypes: + NodeTypes<Payload = ScrollEngineTypes, ChainSpec: ScrollHardforks, Primitives = ScrollPrimitives> +{ +} + +/// Blanket impl for all node types that conform to the Scroll spec. +impl<N> ScrollNodeTypes for N where + N: NodeTypes< + Payload = ScrollEngineTypes, + ChainSpec: ScrollHardforks, + Primitives = ScrollPrimitives, + > +{ +} + +/// Add-ons for the Scroll follower node. +#[derive(Debug)] +pub struct ScrollAddOns<N, RpcMiddleWare = Identity> +where + N: FullNodeComponents<Types: ScrollNodeTypes>, + ScrollEthApiBuilder: EthApiBuilder<N>, +{ + /// Rpc add-ons responsible for launching the RPC servers and instantiating the RPC handlers + /// and eth-api. + pub rpc_add_ons: RpcAddOns< + N, + ScrollEthApiBuilder, + ScrollEngineValidatorBuilder, + BasicEngineApiBuilder<ScrollEngineValidatorBuilder>, + BasicEngineValidatorBuilder<ScrollEngineValidatorBuilder>, + RpcMiddleWare, + >, +} + +impl<N> Default for ScrollAddOns<N, Identity> +where + N: FullNodeComponents<Types: ScrollNodeTypes>, + ScrollEthApiBuilder: EthApiBuilder<N>, +{ + fn default() -> Self { + Self::builder::<Scroll>().build() + } +} + +impl<N, RpcMiddleware> ScrollAddOns<N, RpcMiddleware> +where + N: FullNodeComponents<Types: ScrollNodeTypes>, + ScrollEthApiBuilder: EthApiBuilder<N>, +{ + /// Build a [`ScrollAddOns`] using [`ScrollAddOnsBuilder`]. + pub fn builder<NetworkT>() -> ScrollAddOnsBuilder<NetworkT> { + ScrollAddOnsBuilder::default() + } +} + +impl<N, RpcMiddleware> NodeAddOns<N> for ScrollAddOns<N, RpcMiddleware> +where + N: FullNodeComponents< + Types: NodeTypes< + ChainSpec = ScrollChainSpec, + Primitives = ScrollPrimitives, + Storage = ScrollStorage, + Payload = ScrollEngineTypes, + >, + Evm: ConfigureEngineEvm< + <<N::Types as NodeTypes>::Payload as PayloadTypes>::ExecutionData, + NextBlockEnvCtx = ScrollNextBlockEnvAttributes, + >, + >, + ScrollEthApiError: FromEvmError<N::Evm>, + EvmFactoryFor<N::Evm>: EvmFactory<Tx = ScrollTransactionIntoTxEnv<TxEnv>>, + RpcMiddleware: RethRpcMiddleware, +{ + type Handle = RpcHandle<N, <ScrollEthApiBuilder as EthApiBuilder<N>>::EthApi>; + + async fn launch_add_ons(self, ctx: AddOnsContext<'_, N>) -> eyre::Result<Self::Handle> { + let Self { rpc_add_ons } = self; + rpc_add_ons.launch_add_ons_with(ctx, |_| Ok(())).await + } +} + +impl<N, RpcMiddleware> RethRpcAddOns<N> for ScrollAddOns<N, RpcMiddleware> +where + N: FullNodeComponents< + Types: NodeTypes< + ChainSpec = ScrollChainSpec, + Primitives = ScrollPrimitives, + Storage = ScrollStorage, + Payload = ScrollEngineTypes, + >, + Evm: ConfigureEngineEvm< + <<N::Types as NodeTypes>::Payload as PayloadTypes>::ExecutionData, + NextBlockEnvCtx = ScrollNextBlockEnvAttributes, + >, + >, + ScrollEthApiError: FromEvmError<N::Evm>, + EvmFactoryFor<N::Evm>: EvmFactory<Tx = ScrollTransactionIntoTxEnv<TxEnv>>, + RpcMiddleware: RethRpcMiddleware, +{ + type EthApi = <ScrollEthApiBuilder as EthApiBuilder<N>>::EthApi; + + fn hooks_mut(&mut self) -> &mut reth_node_builder::rpc::RpcHooks<N, Self::EthApi> { + self.rpc_add_ons.hooks_mut() + } +} + +impl<N> EngineValidatorAddOn<N> for ScrollAddOns<N> +where + N: FullNodeComponents< + Types: NodeTypes< + ChainSpec = ScrollChainSpec, + Primitives = ScrollPrimitives, + Payload = ScrollEngineTypes, + >, + Evm: ConfigureEngineEvm< + <<N::Types as NodeTypes>::Payload as PayloadTypes>::ExecutionData, + NextBlockEnvCtx = ScrollNextBlockEnvAttributes, + >, + >, + ScrollEthApiBuilder: EthApiBuilder<N>, +{ + type ValidatorBuilder = BasicEngineValidatorBuilder<ScrollEngineValidatorBuilder>; + + fn engine_validator_builder(&self) -> Self::ValidatorBuilder { + EngineValidatorAddOn::engine_validator_builder(&self.rpc_add_ons) + } +} + +/// A regular scroll evm and executor builder. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct ScrollAddOnsBuilder<NetworkT, RpcMiddleware = Identity> { + /// Sequencer client, configured to forward submitted transactions to sequencer of given Scroll + /// network. + sequencer_url: Option<String>, + /// Minimum suggested priority fee (tip) + min_suggested_priority_fee: u64, + /// Maximum payload size + payload_size_limit: u64, + /// Marker for network types. + _nt: PhantomData<NetworkT>, + /// RPC middleware to use + rpc_middleware: RpcMiddleware, +} + +impl<NetworkT> Default for ScrollAddOnsBuilder<NetworkT> { + fn default() -> Self { + Self { + sequencer_url: None, + payload_size_limit: SCROLL_DEFAULT_PAYLOAD_SIZE_LIMIT, + min_suggested_priority_fee: DEFAULT_MIN_SUGGESTED_PRIORITY_FEE, + _nt: PhantomData, + rpc_middleware: Identity::new(), + } + } +} + +impl<NetworkT, RpcMiddleWare> ScrollAddOnsBuilder<NetworkT, RpcMiddleWare> { + /// With a [`reth_scroll_rpc::SequencerClient`]. + pub fn with_sequencer(mut self, sequencer_client: Option<String>) -> Self { + self.sequencer_url = sequencer_client; + self + } + + /// With minimum suggested priority fee. + pub const fn with_min_suggested_priority_fee( + mut self, + min_suggested_priority_fee: u64, + ) -> Self { + self.min_suggested_priority_fee = min_suggested_priority_fee; + self + } + + /// With maximum payload size limit. + pub const fn with_payload_size_limit(mut self, payload_size_limit: u64) -> Self { + self.payload_size_limit = payload_size_limit; + self + } + + /// Configure the RPC middleware to use + pub fn with_rpc_middleware<T>(self, rpc_middleware: T) -> ScrollAddOnsBuilder<NetworkT, T> { + let Self { sequencer_url, min_suggested_priority_fee, payload_size_limit, _nt, .. } = self; + ScrollAddOnsBuilder { + sequencer_url, + payload_size_limit, + min_suggested_priority_fee, + _nt, + rpc_middleware, + } + } +} + +impl<NetworkT, RpcMiddleWare> ScrollAddOnsBuilder<NetworkT, RpcMiddleWare> { + /// Builds an instance of [`ScrollAddOns`]. + pub fn build<N>(self) -> ScrollAddOns<N, RpcMiddleWare> + where + N: FullNodeComponents<Types: ScrollNodeTypes>, + ScrollEthApiBuilder: EthApiBuilder<N>, + { + let Self { + sequencer_url, + payload_size_limit, + min_suggested_priority_fee, + rpc_middleware, + .. + } = self; + + ScrollAddOns { + rpc_add_ons: RpcAddOns::new( + ScrollEthApiBuilder::new() + .with_sequencer(sequencer_url) + .with_payload_size_limit(payload_size_limit) + .with_min_suggested_priority_fee(min_suggested_priority_fee), + ScrollEngineValidatorBuilder::default(), + BasicEngineApiBuilder::default(), + BasicEngineValidatorBuilder::default(), + rpc_middleware, + ), + } + } +}
diff --git reth/crates/scroll/node/src/args.rs scroll-reth/crates/scroll/node/src/args.rs new file mode 100644 index 0000000000000000000000000000000000000000..7fe563804e2cca31ab5f6ee551183a348821bbe8 --- /dev/null +++ scroll-reth/crates/scroll/node/src/args.rs @@ -0,0 +1,30 @@ +use crate::builder::payload::SCROLL_DEFAULT_PAYLOAD_SIZE_LIMIT; + +use reth_scroll_rpc::eth::DEFAULT_MIN_SUGGESTED_PRIORITY_FEE; + +/// Rollup arguments for the Scroll node. +#[derive(Debug, Clone, clap::Args)] +pub struct ScrollRollupArgs { + /// Endpoint for the sequencer mempool (can be both HTTP and WS) + #[arg(long = "scroll.sequencer")] + pub sequencer: Option<String>, + + /// Minimum suggested priority fee (tip) in wei, default to + /// [`DEFAULT_MIN_SUGGESTED_PRIORITY_FEE`]. + #[arg(long = "scroll.min-suggested-priority-fee", default_value_t = DEFAULT_MIN_SUGGESTED_PRIORITY_FEE)] + pub min_suggested_priority_fee: u64, + + /// Payload size limit, default to `122kB`. + #[arg(long = "scroll.payload-size-limit", default_value_t = SCROLL_DEFAULT_PAYLOAD_SIZE_LIMIT)] + pub payload_size_limit: u64, +} + +impl Default for ScrollRollupArgs { + fn default() -> Self { + Self { + sequencer: None, + min_suggested_priority_fee: DEFAULT_MIN_SUGGESTED_PRIORITY_FEE, + payload_size_limit: SCROLL_DEFAULT_PAYLOAD_SIZE_LIMIT, + } + } +}
diff --git reth/crates/scroll/node/src/builder/consensus.rs scroll-reth/crates/scroll/node/src/builder/consensus.rs new file mode 100644 index 0000000000000000000000000000000000000000..05e6601827df59178f96c703f09b6e0302c0af2c --- /dev/null +++ scroll-reth/crates/scroll/node/src/builder/consensus.rs @@ -0,0 +1,29 @@ +use reth_chainspec::EthChainSpec; +use reth_node_builder::{components::ConsensusBuilder, BuilderContext, FullNodeTypes}; +use reth_node_types::NodeTypes; +use reth_primitives_traits::NodePrimitives; +use reth_scroll_consensus::ScrollBeaconConsensus; +use reth_scroll_primitives::ScrollReceipt; +use scroll_alloy_consensus::ScrollTransaction; +use scroll_alloy_hardforks::ScrollHardforks; +use std::sync::Arc; + +/// The consensus builder for Scroll. +#[derive(Debug, Default, Clone, Copy)] +pub struct ScrollConsensusBuilder; + +impl<Node> ConsensusBuilder<Node> for ScrollConsensusBuilder +where + Node: FullNodeTypes< + Types: NodeTypes< + ChainSpec: EthChainSpec + ScrollHardforks, + Primitives: NodePrimitives<Receipt = ScrollReceipt, SignedTx: ScrollTransaction>, + >, + >, +{ + type Consensus = Arc<ScrollBeaconConsensus<<Node::Types as NodeTypes>::ChainSpec>>; + + async fn build_consensus(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::Consensus> { + Ok(Arc::new(ScrollBeaconConsensus::new(ctx.chain_spec()))) + } +}
diff --git reth/crates/scroll/node/src/builder/engine.rs scroll-reth/crates/scroll/node/src/builder/engine.rs new file mode 100644 index 0000000000000000000000000000000000000000..09a564b2afa7b4392662b1a306f070ce87686adc --- /dev/null +++ scroll-reth/crates/scroll/node/src/builder/engine.rs @@ -0,0 +1,159 @@ +use crate::addons::ScrollNodeTypes; +use std::sync::Arc; + +use alloy_consensus::BlockHeader; +use alloy_rpc_types_engine::{ExecutionData, PayloadError}; +use reth_node_api::{ + AddOnsContext, EngineApiMessageVersion, EngineApiValidator, EngineObjectValidationError, + ExecutionPayload, FullNodeComponents, InvalidPayloadAttributesError, MessageValidationKind, + NewPayloadError, PayloadAttributes, PayloadOrAttributes, PayloadTypes, PayloadValidator, + VersionSpecificValidationError, +}; +use reth_node_builder::rpc::PayloadValidatorBuilder; +use reth_node_types::NodeTypes; +use reth_primitives_traits::{Block, RecoveredBlock}; +use reth_scroll_consensus::{CLIQUE_IN_TURN_DIFFICULTY, CLIQUE_NO_TURN_DIFFICULTY}; +use reth_scroll_engine_primitives::try_into_block; +use reth_scroll_primitives::ScrollBlock; +use scroll_alloy_hardforks::ScrollHardforks; +use scroll_alloy_rpc_types_engine::ScrollPayloadAttributes; + +/// Builder for [`ScrollEngineValidator`]. +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct ScrollEngineValidatorBuilder; + +impl<Node> PayloadValidatorBuilder<Node> for ScrollEngineValidatorBuilder +where + Node: FullNodeComponents<Types: ScrollNodeTypes>, +{ + type Validator = ScrollEngineValidator<<Node::Types as NodeTypes>::ChainSpec>; + + async fn build(self, ctx: &AddOnsContext<'_, Node>) -> eyre::Result<Self::Validator> { + Ok(ScrollEngineValidator::new(ctx.config.chain.clone())) + } +} + +/// Scroll engine validator. +#[derive(Debug, Clone)] +pub struct ScrollEngineValidator<CS> { + chainspec: Arc<CS>, +} + +impl<CS> ScrollEngineValidator<CS> { + /// Returns a new [`ScrollEngineValidator`]. + pub const fn new(chainspec: Arc<CS>) -> Self { + Self { chainspec } + } +} + +impl<CS, Types> EngineApiValidator<Types> for ScrollEngineValidator<CS> +where + Types: PayloadTypes<PayloadAttributes = ScrollPayloadAttributes, ExecutionData = ExecutionData>, + CS: ScrollHardforks + Send + Sync + 'static, +{ + fn validate_version_specific_fields( + &self, + _version: EngineApiMessageVersion, + payload_or_attrs: PayloadOrAttributes<'_, Types::ExecutionData, ScrollPayloadAttributes>, + ) -> Result<(), EngineObjectValidationError> { + validate_scroll_payload_or_attributes( + &payload_or_attrs, + payload_or_attrs.message_validation_kind(), + )?; + Ok(()) + } + + fn ensure_well_formed_attributes( + &self, + _version: EngineApiMessageVersion, + attributes: &ScrollPayloadAttributes, + ) -> Result<(), EngineObjectValidationError> { + validate_scroll_payload_or_attributes( + &PayloadOrAttributes::PayloadAttributes::<'_, ExecutionData, _>(attributes), + MessageValidationKind::PayloadAttributes, + )?; + + // ensure block data hint is present pre euclid. + let is_euclid_active = + self.chainspec.is_euclid_active_at_timestamp(attributes.payload_attributes.timestamp); + if !is_euclid_active && attributes.block_data_hint.is_empty() { + return Err(EngineObjectValidationError::InvalidParams( + "Missing block data hint Pre-Euclid".to_string().into(), + )); + } + + Ok(()) + } +} + +/// Validates the payload or attributes for Scroll. +fn validate_scroll_payload_or_attributes<Payload: ExecutionPayload>( + payload_or_attributes: &PayloadOrAttributes<'_, Payload, ScrollPayloadAttributes>, + message_validation_kind: MessageValidationKind, +) -> Result<(), EngineObjectValidationError> { + if payload_or_attributes.parent_beacon_block_root().is_some() { + return Err(message_validation_kind + .to_error(VersionSpecificValidationError::ParentBeaconBlockRootNotSupportedBeforeV3)); + } + if payload_or_attributes.withdrawals().is_some() { + return Err(message_validation_kind + .to_error(VersionSpecificValidationError::HasWithdrawalsPreShanghai)); + } + + Ok(()) +} + +impl<CS, Types> PayloadValidator<Types> for ScrollEngineValidator<CS> +where + Types: PayloadTypes<ExecutionData = ExecutionData>, + CS: ScrollHardforks + Send + Sync + 'static, +{ + type Block = ScrollBlock; + + fn ensure_well_formed_payload( + &self, + payload: ExecutionData, + ) -> Result<RecoveredBlock<Self::Block>, NewPayloadError> { + let expected_hash = payload.payload.block_hash(); + + // First parse the block + let mut block = try_into_block(payload, self.chainspec.clone())?; + + // Seal the block with the no-turn difficulty and return if hashes match. + // We guess the difficulty, which should always be 1 or 2 on Scroll. + // CLIQUE_NO_TURN_DIFFICULTY is used starting at Euclid, so we test this value first. + block.header.difficulty = CLIQUE_NO_TURN_DIFFICULTY; + let block_hash_no_turn = block.hash_slow(); + if block_hash_no_turn == expected_hash { + return block + .seal_unchecked(block_hash_no_turn) + .try_recover() + .map_err(|err| NewPayloadError::Other(err.into())); + } + + // Seal the block with the in-turn difficulty and return if hashes match + block.header.difficulty = CLIQUE_IN_TURN_DIFFICULTY; + let block_hash_in_turn = block.hash_slow(); + if block_hash_in_turn == expected_hash { + return block + .seal_unchecked(block_hash_in_turn) + .try_recover() + .map_err(|err| NewPayloadError::Other(err.into())); + } + + Err(PayloadError::BlockHash { execution: block_hash_no_turn, consensus: expected_hash } + .into()) + } + + fn validate_payload_attributes_against_header( + &self, + attr: &Types::PayloadAttributes, + header: &<Self::Block as Block>::Header, + ) -> Result<(), InvalidPayloadAttributesError> { + if attr.timestamp() < header.timestamp() { + return Err(InvalidPayloadAttributesError::InvalidTimestamp); + } + Ok(()) + } +}
diff --git reth/crates/scroll/node/src/builder/execution.rs scroll-reth/crates/scroll/node/src/builder/execution.rs new file mode 100644 index 0000000000000000000000000000000000000000..2e56b699cab9ae2e1bd5e4169404b90555ec40ba --- /dev/null +++ scroll-reth/crates/scroll/node/src/builder/execution.rs @@ -0,0 +1,24 @@ +use reth_node_builder::{components::ExecutorBuilder, BuilderContext, FullNodeTypes}; +use reth_node_types::NodeTypes; +use reth_scroll_chainspec::ScrollChainSpec; +use reth_scroll_evm::ScrollEvmConfig; +use reth_scroll_primitives::ScrollPrimitives; + +/// Executor builder for Scroll. +#[derive(Debug, Default, Clone, Copy)] +#[non_exhaustive] +pub struct ScrollExecutorBuilder; + +impl<Node> ExecutorBuilder<Node> for ScrollExecutorBuilder +where + Node: FullNodeTypes, + Node::Types: NodeTypes<ChainSpec = ScrollChainSpec, Primitives = ScrollPrimitives>, +{ + type EVM = ScrollEvmConfig; + + async fn build_evm(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::EVM> { + let evm_config = ScrollEvmConfig::scroll(ctx.chain_spec()); + + Ok(evm_config) + } +}
diff --git reth/crates/scroll/node/src/builder/mod.rs scroll-reth/crates/scroll/node/src/builder/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..86ed6d848601ad99def48042f8331f48e688b84d --- /dev/null +++ scroll-reth/crates/scroll/node/src/builder/mod.rs @@ -0,0 +1,6 @@ +pub(crate) mod consensus; +pub(crate) mod engine; +pub(crate) mod execution; +pub(crate) mod network; +pub(crate) mod payload; +pub(crate) mod pool;
diff --git reth/crates/scroll/node/src/builder/network.rs scroll-reth/crates/scroll/node/src/builder/network.rs new file mode 100644 index 0000000000000000000000000000000000000000..8fd140f88d5893a6393c88e40090cd31f1d90190 --- /dev/null +++ scroll-reth/crates/scroll/node/src/builder/network.rs @@ -0,0 +1,111 @@ +use reth_eth_wire_types::BasicNetworkPrimitives; +use reth_network::{ + config::NetworkMode, + protocol::{RlpxSubProtocol, RlpxSubProtocols}, + transform::header::HeaderTransform, + NetworkConfig, NetworkHandle, NetworkManager, PeersInfo, +}; +use reth_node_api::TxTy; +use reth_node_builder::{components::NetworkBuilder, BuilderContext, FullNodeTypes}; +use reth_node_types::NodeTypes; +use reth_primitives_traits::BlockHeader; +use reth_scroll_chainspec::ScrollChainSpec; +use reth_scroll_primitives::ScrollPrimitives; +use reth_tracing::tracing::info; +use reth_transaction_pool::{PoolTransaction, TransactionPool}; +use scroll_alloy_hardforks::ScrollHardforks; +use std::fmt::Debug; + +/// The network builder for Scroll. +#[derive(Debug, Default)] +pub struct ScrollNetworkBuilder { + /// Additional `RLPx` sub-protocols to be added to the network. + scroll_sub_protocols: RlpxSubProtocols, +} + +impl ScrollNetworkBuilder { + /// Create a new [`ScrollNetworkBuilder`] with default configuration. + pub fn new() -> Self { + Self { scroll_sub_protocols: RlpxSubProtocols::default() } + } + + /// Add a scroll sub-protocol to the network builder. + pub fn with_sub_protocol(mut self, protocol: RlpxSubProtocol) -> Self { + self.scroll_sub_protocols.push(protocol); + self + } +} + +impl<Node, Pool> NetworkBuilder<Node, Pool> for ScrollNetworkBuilder +where + Node: + FullNodeTypes<Types: NodeTypes<ChainSpec = ScrollChainSpec, Primitives = ScrollPrimitives>>, + Pool: TransactionPool< + Transaction: PoolTransaction< + Consensus = TxTy<Node::Types>, + Pooled = scroll_alloy_consensus::ScrollPooledTransaction, + >, + > + Unpin + + 'static, +{ + type Network = NetworkHandle<ScrollNetworkPrimitives>; + + async fn build_network( + self, + ctx: &BuilderContext<Node>, + pool: Pool, + ) -> eyre::Result<Self::Network> { + // get the header transform. + let chain_spec = ctx.chain_spec(); + let transform = ScrollHeaderTransform { chain_spec }; + + // set the network mode to work. + let config = ctx.network_config()?; + let config = NetworkConfig { + network_mode: NetworkMode::Work, + header_transform: Box::new(transform), + extra_protocols: self.scroll_sub_protocols, + ..config + }; + + let network = NetworkManager::builder(config).await?; + let handle = ctx.start_network(network, pool); + info!(target: "reth::cli", enode=%handle.local_node_record(), "P2P networking initialized"); + Ok(handle) + } +} + +/// Network primitive types used by Scroll networks. +pub type ScrollNetworkPrimitives = + BasicNetworkPrimitives<ScrollPrimitives, scroll_alloy_consensus::ScrollPooledTransaction>; + +/// An implementation of a [`HeaderTransform`] for Scroll. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct ScrollHeaderTransform<ChainSpec> { + chain_spec: ChainSpec, +} + +impl<ChainSpec: ScrollHardforks + Debug + Send + Sync + 'static> ScrollHeaderTransform<ChainSpec> { + /// Returns a new instance of the [`ScrollHeaderTransform`] from the provider chain spec. + pub const fn new(chain_spec: ChainSpec) -> Self { + Self { chain_spec } + } + + /// Returns a new [`ScrollHeaderTransform`] as a [`HeaderTransform`] trait object. + pub fn boxed<H: BlockHeader>(chain_spec: ChainSpec) -> Box<dyn HeaderTransform<H>> { + Box::new(Self { chain_spec }) + } +} + +impl<H: BlockHeader, ChainSpec: ScrollHardforks + Debug + Send + Sync> HeaderTransform<H> + for ScrollHeaderTransform<ChainSpec> +{ + fn map(&self, mut header: H) -> H { + if self.chain_spec.is_euclid_v2_active_at_timestamp(header.timestamp()) { + // clear the extra data field. + *header.extra_data_mut() = Default::default() + } + header + } +}
diff --git reth/crates/scroll/node/src/builder/payload.rs scroll-reth/crates/scroll/node/src/builder/payload.rs new file mode 100644 index 0000000000000000000000000000000000000000..25761255bac35c31ca7be344251b759aa3ec6928 --- /dev/null +++ scroll-reth/crates/scroll/node/src/builder/payload.rs @@ -0,0 +1,113 @@ +use reth_evm::ConfigureEvm; +use reth_node_api::PrimitivesTy; +use reth_node_builder::{ + components::PayloadBuilderBuilder, BuilderContext, FullNodeTypes, PayloadBuilderConfig, +}; +use reth_node_types::{NodeTypes, TxTy}; +use reth_scroll_chainspec::ScrollChainSpec; +use reth_scroll_engine_primitives::ScrollEngineTypes; +use reth_scroll_evm::ScrollNextBlockEnvAttributes; +use reth_scroll_payload::{ScrollBuilderConfig, ScrollPayloadTransactions}; +use reth_scroll_primitives::{ScrollPrimitives, ScrollTransactionSigned}; +use reth_transaction_pool::{PoolTransaction, TransactionPool}; +use std::time::Duration; + +/// Payload builder for Scroll. +#[derive(Debug, Clone, Copy)] +pub struct ScrollPayloadBuilderBuilder<Txs = ()> { + /// Returns the current best transactions from the mempool. + pub best_transactions: Txs, + /// The payload building time limit. + pub payload_building_time_limit: Duration, + /// The block DA size limit. + pub block_da_size_limit: Option<u64>, +} + +impl Default for ScrollPayloadBuilderBuilder { + fn default() -> Self { + Self { + best_transactions: (), + payload_building_time_limit: SCROLL_PAYLOAD_BUILDING_DURATION, + block_da_size_limit: Some(SCROLL_DEFAULT_PAYLOAD_SIZE_LIMIT), + } + } +} + +const SCROLL_GAS_LIMIT: u64 = 20_000_000; +const SCROLL_PAYLOAD_BUILDING_DURATION: Duration = Duration::from_secs(1); +pub(crate) const SCROLL_DEFAULT_PAYLOAD_SIZE_LIMIT: u64 = 122_880; + +impl<Txs> ScrollPayloadBuilderBuilder<Txs> { + /// A helper method to initialize [`reth_scroll_payload::ScrollPayloadBuilder`] with the + /// given EVM config. + pub fn build<Node, Evm, Pool>( + self, + evm_config: Evm, + ctx: &BuilderContext<Node>, + pool: Pool, + ) -> eyre::Result<reth_scroll_payload::ScrollPayloadBuilder<Pool, Node::Provider, Evm, Txs>> + where + Node: FullNodeTypes< + Types: NodeTypes< + Payload = ScrollEngineTypes, + ChainSpec = ScrollChainSpec, + Primitives = ScrollPrimitives, + >, + >, + Pool: TransactionPool<Transaction: PoolTransaction<Consensus = TxTy<Node::Types>>> + + Unpin + + 'static, + Evm: ConfigureEvm<Primitives = PrimitivesTy<Node::Types>>, + Txs: ScrollPayloadTransactions<Pool::Transaction>, + { + let gas_limit = ctx.payload_builder_config().gas_limit().unwrap_or_else (|| { + tracing::warn!(target: "reth::cli", "Using {SCROLL_GAS_LIMIT} gas limit for ScrollPayloadBuilder. Configure with --builder.gaslimit"); + SCROLL_GAS_LIMIT + }); + + let payload_builder = reth_scroll_payload::ScrollPayloadBuilder::new( + pool, + evm_config, + ctx.provider().clone(), + ScrollBuilderConfig::new( + Some(gas_limit), + self.payload_building_time_limit, + self.block_da_size_limit, + ), + ) + .with_transactions(self.best_transactions); + + Ok(payload_builder) + } +} + +impl<Node, Pool, Txs, Evm> PayloadBuilderBuilder<Node, Pool, Evm> + for ScrollPayloadBuilderBuilder<Txs> +where + Node: FullNodeTypes< + Types: NodeTypes< + Payload = ScrollEngineTypes, + ChainSpec = ScrollChainSpec, + Primitives = ScrollPrimitives, + >, + >, + Evm: ConfigureEvm< + Primitives = PrimitivesTy<Node::Types>, + NextBlockEnvCtx = ScrollNextBlockEnvAttributes, + > + 'static, + Pool: TransactionPool<Transaction: PoolTransaction<Consensus = ScrollTransactionSigned>> + + Unpin + + 'static, + Txs: ScrollPayloadTransactions<Pool::Transaction>, +{ + type PayloadBuilder = reth_scroll_payload::ScrollPayloadBuilder<Pool, Node::Provider, Evm, Txs>; + + async fn build_payload_builder( + self, + ctx: &BuilderContext<Node>, + pool: Pool, + evm_config: Evm, + ) -> eyre::Result<Self::PayloadBuilder> { + self.build(evm_config, ctx, pool) + } +}
diff --git reth/crates/scroll/node/src/builder/pool.rs scroll-reth/crates/scroll/node/src/builder/pool.rs new file mode 100644 index 0000000000000000000000000000000000000000..8fead3e4c3f2d394dff843e1509ebaf74e921b75 --- /dev/null +++ scroll-reth/crates/scroll/node/src/builder/pool.rs @@ -0,0 +1,382 @@ +use reth_chainspec::EthChainSpec; +use reth_node_api::{FullNodeTypes, NodeTypes}; +use reth_node_builder::{ + components::{PoolBuilder, PoolBuilderConfigOverrides}, + BuilderContext, TxTy, +}; + +use reth_provider::CanonStateSubscriptions; +use reth_scroll_chainspec::{ChainConfig, ScrollChainConfig}; +use reth_scroll_evm::ScrollBaseFeeProvider; +use reth_scroll_txpool::{ScrollTransactionPool, ScrollTransactionValidator}; +use reth_transaction_pool::{ + blobstore::DiskFileBlobStore, CoinbaseTipOrdering, EthPoolTransaction, + TransactionValidationTaskExecutor, +}; +use scroll_alloy_consensus::ScrollTransaction; +use scroll_alloy_hardforks::ScrollHardforks; + +/// A basic scroll transaction pool. +/// +/// This contains various settings that can be configured and take precedence over the node's +/// config. +#[derive(Debug, Clone)] +pub struct ScrollPoolBuilder<T = reth_scroll_txpool::ScrollPooledTransaction> { + /// Enforced overrides that are applied to the pool config. + pub pool_config_overrides: PoolBuilderConfigOverrides, + + /// Marker for the pooled transaction type. + _pd: core::marker::PhantomData<T>, +} + +impl<T> Default for ScrollPoolBuilder<T> { + fn default() -> Self { + Self { pool_config_overrides: Default::default(), _pd: Default::default() } + } +} + +impl<T> ScrollPoolBuilder<T> { + /// Sets the [`PoolBuilderConfigOverrides`] on the pool builder. + pub fn with_pool_config_overrides( + mut self, + pool_config_overrides: PoolBuilderConfigOverrides, + ) -> Self { + self.pool_config_overrides = pool_config_overrides; + self + } +} + +impl<Node, T> PoolBuilder<Node> for ScrollPoolBuilder<T> +where + Node: FullNodeTypes< + Types: NodeTypes< + ChainSpec: EthChainSpec + ScrollHardforks + ChainConfig<Config = ScrollChainConfig>, + >, + >, + T: EthPoolTransaction<Consensus = TxTy<Node::Types>> + ScrollTransaction, +{ + type Pool = ScrollTransactionPool<Node::Provider, DiskFileBlobStore, T>; + + async fn build_pool(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::Pool> { + let Self { pool_config_overrides, .. } = self; + let data_dir = ctx.config().datadir(); + let blob_store = DiskFileBlobStore::open(data_dir.blobstore(), Default::default())?; + + let validator = TransactionValidationTaskExecutor::eth_builder(ctx.provider().clone()) + .no_eip4844() + .with_head_timestamp(ctx.head().timestamp) + .kzg_settings(ctx.kzg_settings()?) + .with_local_transactions_config( + pool_config_overrides.clone().apply(ctx.pool_config()).local_transactions_config, + ) + .with_max_tx_input_bytes(ctx.chain_spec().chain_config().max_tx_payload_bytes_per_block) + .with_additional_tasks( + pool_config_overrides + .additional_validation_tasks + .unwrap_or_else(|| ctx.config().txpool.additional_validation_tasks), + ) + .build_with_tasks(ctx.task_executor().clone(), blob_store.clone()) + .map(|validator| { + ScrollTransactionValidator::new(validator) + // In --dev mode we can't require gas fees because we're unable to decode + // the L1 block info + .require_l1_data_gas_fee(!ctx.config().dev.dev) + }); + + let transaction_pool = reth_transaction_pool::Pool::new( + validator, + CoinbaseTipOrdering::default(), + blob_store, + pool_config_overrides.apply(ctx.pool_config()), + ); + tracing::info!(target: "reth::cli", "Transaction pool initialized"); + let transactions_path = data_dir.txpool_transactions(); + + // spawn txpool maintenance tasks + { + let chain_events = ctx.provider().canonical_state_stream(); + let client = ctx.provider().clone(); + let transactions_backup_config = + reth_transaction_pool::maintain::LocalTransactionBackupConfig::with_local_txs_backup(transactions_path); + let base_fee_provider = ScrollBaseFeeProvider::new(ctx.chain_spec()); + + ctx.task_executor().spawn_critical_with_graceful_shutdown_signal( + "local transactions backup task", + |shutdown| { + reth_transaction_pool::maintain::backup_local_transactions_task( + shutdown, + transaction_pool.clone(), + transactions_backup_config, + ) + }, + ); + + // spawn the main maintenance task + ctx.task_executor().spawn_critical( + "txpool maintenance task", + reth_transaction_pool::maintain::maintain_transaction_pool_future( + client, + base_fee_provider, + transaction_pool.clone(), + chain_events, + ctx.task_executor().clone(), + reth_transaction_pool::maintain::MaintainPoolConfig { + max_tx_lifetime: transaction_pool.config().max_queued_lifetime, + ..Default::default() + }, + ), + ); + tracing::debug!(target: "reth::cli", "Spawned txpool maintenance task"); + } + + Ok(transaction_pool) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ScrollNode; + + use alloy_consensus::{transaction::Recovered, Header, Signed, TxLegacy}; + use alloy_primitives::{private::rand::random_iter, Bytes, Sealed, Signature, B256, U256}; + use reth_chainspec::Head; + use reth_db::mock::DatabaseMock; + use reth_node_api::FullNodeTypesAdapter; + use reth_node_builder::common::WithConfigs; + use reth_node_core::node_config::NodeConfig; + use reth_primitives_traits::{ + transaction::error::InvalidTransactionError, GotExpected, GotExpectedBoxed, + }; + use reth_provider::{ + noop::NoopProvider, + test_utils::{ExtendedAccount, MockEthProvider}, + }; + use reth_scroll_chainspec::{ScrollChainSpec, SCROLL_DEV, SCROLL_MAINNET}; + use reth_scroll_primitives::{ScrollBlock, ScrollPrimitives}; + use reth_scroll_txpool::ScrollPooledTransaction; + use reth_tasks::TaskManager; + use reth_transaction_pool::{ + blobstore::NoopBlobStore, + error::{InvalidPoolTransactionError, PoolErrorKind}, + PoolConfig, TransactionOrigin, TransactionPool, + }; + use scroll_alloy_consensus::{ScrollTxEnvelope, TxL1Message}; + use scroll_alloy_evm::curie::L1_GAS_PRICE_ORACLE_ADDRESS; + + async fn pool() -> ( + ScrollTransactionPool<NoopProvider<ScrollChainSpec, ScrollPrimitives>, DiskFileBlobStore>, + TaskManager, + ) { + let handle = tokio::runtime::Handle::current(); + let manager = TaskManager::new(handle); + let config = WithConfigs { + config: NodeConfig::new(SCROLL_MAINNET.clone()), + toml_config: Default::default(), + }; + + let pool_builder = ScrollPoolBuilder::<ScrollPooledTransaction>::default(); + let ctx = BuilderContext::< + FullNodeTypesAdapter< + ScrollNode, + DatabaseMock, + NoopProvider<ScrollChainSpec, ScrollPrimitives>, + >, + >::new( + Head::default(), + NoopProvider::new(SCROLL_MAINNET.clone()), + manager.executor(), + config, + ); + (pool_builder.build_pool(&ctx).await.unwrap(), manager) + } + + #[tokio::test] + async fn test_validate_one_oversized_transaction() { + // create the pool. + let (pool, manager) = pool().await; + let tx = ScrollTxEnvelope::Legacy(Signed::new_unchecked( + TxLegacy { gas_limit: 21_000, ..Default::default() }, + Signature::new(U256::ZERO, U256::ZERO, false), + Default::default(), + )); + + // Create a pool transaction with an encoded length of 123,904 bytes. + let pool_tx = ScrollPooledTransaction::new( + Recovered::new_unchecked(tx, Default::default()), + 121 * 1024, + ); + + // add the transaction to the pool and expect an `OversizedData` error. + let err = pool.add_transaction(TransactionOrigin::Local, pool_tx).await.unwrap_err(); + assert!(matches!( + err.kind, + PoolErrorKind::InvalidTransaction( + InvalidPoolTransactionError::OversizedData(x, y,) + ) if x == 121*1024 && y == 120*1024, + )); + + // explicitly drop the manager here otherwise the `TransactionValidationTaskExecutor` will + // drop all validation tasks. + drop(manager); + } + + #[tokio::test] + async fn test_validate_one_rollup_fee_exceeds_limit() { + // create the client. + let handle = tokio::runtime::Handle::current(); + let manager = TaskManager::new(handle); + let blob_store = NoopBlobStore::default(); + let signer = Default::default(); + let client = + MockEthProvider::<ScrollPrimitives, _>::new().with_chain_spec(SCROLL_DEV.clone()); + let hash = B256::random(); + + // load a header, block, signer and the L1_GAS_PRICE_ORACLE_ADDRESS storage. + client.add_header(hash, Header::default()); + client.add_block(hash, ScrollBlock::default()); + client.add_account(signer, ExtendedAccount::new(0, U256::from(400_000))); + client.add_account( + L1_GAS_PRICE_ORACLE_ADDRESS, + ExtendedAccount::new(0, U256::from(400_000)).extend_storage( + (0u8..8).map(|k| (B256::from(U256::from(k)), U256::from(u64::MAX))), + ), + ); + + // create the validation task. + let validator = TransactionValidationTaskExecutor::eth_builder(client) + .no_eip4844() + .build_with_tasks(manager.executor(), blob_store) + .map(|validator| { + ScrollTransactionValidator::new(validator).require_l1_data_gas_fee(true) + }); + + // create the pool. + let pool = ScrollTransactionPool::new( + validator, + CoinbaseTipOrdering::<ScrollPooledTransaction>::default(), + NoopBlobStore::default(), + PoolConfig::default(), + ); + + // prepare a transaction with random input. + let tx = ScrollTxEnvelope::Legacy(Signed::new_unchecked( + TxLegacy { + gas_limit: 55_000, + gas_price: 7, + input: Bytes::from(random_iter::<u8>().take(100).collect::<Vec<_>>()), + ..Default::default() + }, + Signature::new(U256::ZERO, U256::ZERO, false), + Default::default(), + )); + let pool_tx = + ScrollPooledTransaction::new(Recovered::new_unchecked(tx, signer), 120 * 1024); + + // add the transaction in the pool and expect to hit `InsufficientFunds` error. + let err = pool.add_transaction(TransactionOrigin::Local, pool_tx).await.unwrap_err(); + assert!(matches!( + err.kind, + PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Consensus( + InvalidTransactionError::GasUintOverflow + )) + )); + + // explicitly drop the manager here otherwise the `TransactionValidationTaskExecutor` will + // drop all validation tasks. + drop(manager); + } + + #[tokio::test] + async fn test_validate_one_rollup_fee_exceeds_balance() { + // create the client. + let handle = tokio::runtime::Handle::current(); + let manager = TaskManager::new(handle); + let blob_store = NoopBlobStore::default(); + let signer = Default::default(); + let client = + MockEthProvider::<ScrollPrimitives, _>::new().with_chain_spec(SCROLL_DEV.clone()); + let hash = B256::random(); + + // load a header, block, signer and the L1_GAS_PRICE_ORACLE_ADDRESS storage. + client.add_header(hash, Header::default()); + client.add_block(hash, ScrollBlock::default()); + client.add_account(signer, ExtendedAccount::new(0, U256::from(400_000))); + client.add_account( + L1_GAS_PRICE_ORACLE_ADDRESS, + ExtendedAccount::new(0, U256::from(400_000)).extend_storage( + (0u8..8).map(|k| (B256::from(U256::from(k)), U256::from(u32::MAX))), + ), + ); + + // create the validation task. + let validator = TransactionValidationTaskExecutor::eth_builder(client) + .no_eip4844() + .build_with_tasks(manager.executor(), blob_store) + .map(|validator| { + ScrollTransactionValidator::new(validator).require_l1_data_gas_fee(true) + }); + + // create the pool. + let pool = ScrollTransactionPool::new( + validator, + CoinbaseTipOrdering::<ScrollPooledTransaction>::default(), + NoopBlobStore::default(), + PoolConfig::default(), + ); + + // prepare a transaction with random input. + let tx = ScrollTxEnvelope::Legacy(Signed::new_unchecked( + TxLegacy { + gas_limit: 55_000, + gas_price: 7, + input: Bytes::from(random_iter::<u8>().take(100).collect::<Vec<_>>()), + ..Default::default() + }, + Signature::new(U256::ZERO, U256::ZERO, false), + Default::default(), + )); + let pool_tx = + ScrollPooledTransaction::new(Recovered::new_unchecked(tx, signer), 120 * 1024); + + // add the transaction in the pool and expect to hit `InsufficientFunds` error. + let err = pool.add_transaction(TransactionOrigin::Local, pool_tx).await.unwrap_err(); + assert!(matches!( + err.kind, + PoolErrorKind::InvalidTransaction( + InvalidPoolTransactionError::Consensus(InvalidTransactionError::InsufficientFunds(GotExpectedBoxed(expected))) + ) if *expected == GotExpected{ got: U256::from(400000), expected: U256::from(4205858031847u64) } + )); + + // explicitly drop the manager here otherwise the `TransactionValidationTaskExecutor` will + // drop all validation tasks. + drop(manager); + } + + #[tokio::test] + async fn test_validate_one_disallow_l1_messages() { + // create the pool. + let (pool, manager) = pool().await; + let tx = ScrollTxEnvelope::L1Message(Sealed::new_unchecked( + TxL1Message::default(), + B256::default(), + )); + + // Create a pool transaction with the L1 message. + let pool_tx = + ScrollPooledTransaction::new(Recovered::new_unchecked(tx, Default::default()), 0); + + // add the transaction to the pool and expect an `OversizedData` error. + let err = pool.add_transaction(TransactionOrigin::Local, pool_tx).await.unwrap_err(); + assert!(matches!( + err.kind, + PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Consensus( + InvalidTransactionError::TxTypeNotSupported + )) + )); + + // explicitly drop the manager here otherwise the `TransactionValidationTaskExecutor` will + // drop all validation tasks. + drop(manager); + } +}
diff --git reth/crates/scroll/node/src/lib.rs scroll-reth/crates/scroll/node/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..103e1b20d8f6fd382089cdfffa73e5b2294c095a --- /dev/null +++ scroll-reth/crates/scroll/node/src/lib.rs @@ -0,0 +1,29 @@ +//! Node specific implementations for Scroll. + +mod args; +pub use args::ScrollRollupArgs; + +mod builder; +pub use builder::{ + consensus::ScrollConsensusBuilder, + engine::{ScrollEngineValidator, ScrollEngineValidatorBuilder}, + execution::ScrollExecutorBuilder, + network::{ScrollHeaderTransform, ScrollNetworkBuilder, ScrollNetworkPrimitives}, + payload::ScrollPayloadBuilderBuilder, + pool::ScrollPoolBuilder, +}; + +mod addons; +pub use addons::{ScrollAddOns, ScrollAddOnsBuilder, ScrollNodeTypes}; + +mod node; +pub use node::ScrollNode; + +mod storage; +pub use storage::ScrollStorage; + +/// Helpers for running test node instances. +#[cfg(feature = "test-utils")] +pub mod test_utils; + +pub use reth_scroll_engine_primitives::{ScrollBuiltPayload, ScrollPayloadBuilderAttributes};
diff --git reth/crates/scroll/node/src/node.rs scroll-reth/crates/scroll/node/src/node.rs new file mode 100644 index 0000000000000000000000000000000000000000..17832c91290fe9ddb1d947e91ff60e34cc5a5420 --- /dev/null +++ scroll-reth/crates/scroll/node/src/node.rs @@ -0,0 +1,119 @@ +//! Node specific implementations for Scroll. + +use crate::{ + args::ScrollRollupArgs, ScrollAddOns, ScrollAddOnsBuilder, ScrollConsensusBuilder, + ScrollExecutorBuilder, ScrollNetworkBuilder, ScrollPayloadBuilderBuilder, ScrollPoolBuilder, + ScrollStorage, +}; +use reth_engine_local::LocalPayloadAttributesBuilder; +use reth_node_api::{FullNodeComponents, PayloadAttributesBuilder, PayloadTypes}; +use reth_node_builder::{ + components::{BasicPayloadServiceBuilder, ComponentsBuilder}, + node::{FullNodeTypes, NodeTypes}, + DebugNode, Node, NodeAdapter, NodeComponentsBuilder, +}; +use reth_scroll_chainspec::ScrollChainSpec; +use reth_scroll_engine_primitives::ScrollEngineTypes; +use reth_scroll_primitives::ScrollPrimitives; +use reth_trie_db::MerklePatriciaTrie; +use scroll_alloy_network::Scroll; +use std::sync::Arc; + +/// The Scroll node implementation. +#[derive(Clone, Debug, Default)] +#[non_exhaustive] +pub struct ScrollNode { + /// Additional Scroll args. + pub args: ScrollRollupArgs, +} + +impl ScrollNode { + /// Creates a new instance of the Scroll node type. + pub const fn new(args: ScrollRollupArgs) -> Self { + Self { args } + } + + /// Returns a [`ComponentsBuilder`] configured for a regular Ethereum node. + pub fn components<Node>() -> ComponentsBuilder< + Node, + ScrollPoolBuilder, + BasicPayloadServiceBuilder<ScrollPayloadBuilderBuilder>, + ScrollNetworkBuilder, + ScrollExecutorBuilder, + ScrollConsensusBuilder, + > + where + Node: FullNodeTypes< + Types: NodeTypes< + ChainSpec = ScrollChainSpec, + Primitives = ScrollPrimitives, + Payload = ScrollEngineTypes, + >, + >, + { + ComponentsBuilder::default() + .node_types::<Node>() + .pool(ScrollPoolBuilder::default()) + .executor(ScrollExecutorBuilder::default()) + .payload(BasicPayloadServiceBuilder::new(ScrollPayloadBuilderBuilder::default())) + .network(ScrollNetworkBuilder::new()) + .executor(ScrollExecutorBuilder) + .consensus(ScrollConsensusBuilder) + } +} + +impl<N> Node<N> for ScrollNode +where + N: FullNodeTypes<Types = Self>, +{ + type ComponentsBuilder = ComponentsBuilder< + N, + ScrollPoolBuilder, + BasicPayloadServiceBuilder<ScrollPayloadBuilderBuilder>, + ScrollNetworkBuilder, + ScrollExecutorBuilder, + ScrollConsensusBuilder, + >; + + type AddOns = ScrollAddOns< + NodeAdapter<N, <Self::ComponentsBuilder as NodeComponentsBuilder<N>>::Components>, + >; + + fn components_builder(&self) -> Self::ComponentsBuilder { + Self::components() + } + + fn add_ons(&self) -> Self::AddOns { + ScrollAddOnsBuilder::<Scroll, _>::default() + .with_sequencer(self.args.sequencer.clone()) + .with_min_suggested_priority_fee(self.args.min_suggested_priority_fee) + .with_payload_size_limit(self.args.payload_size_limit) + .build() + } +} + +impl<N> DebugNode<N> for ScrollNode +where + N: FullNodeComponents<Types = Self>, +{ + type RpcBlock = alloy_rpc_types_eth::Block<scroll_alloy_consensus::ScrollTxEnvelope>; + + fn rpc_to_primitive_block(rpc_block: Self::RpcBlock) -> reth_node_api::BlockTy<Self> { + rpc_block.into_consensus() + } + + fn local_payload_attributes_builder( + chain_spec: &Self::ChainSpec, + ) -> impl PayloadAttributesBuilder<<<Self as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes> + { + LocalPayloadAttributesBuilder::new(Arc::new(chain_spec.clone())) + } +} + +impl NodeTypes for ScrollNode { + type Primitives = ScrollPrimitives; + type ChainSpec = ScrollChainSpec; + type StateCommitment = MerklePatriciaTrie; + type Storage = ScrollStorage; + type Payload = ScrollEngineTypes; +}
diff --git reth/crates/scroll/node/src/storage.rs scroll-reth/crates/scroll/node/src/storage.rs new file mode 100644 index 0000000000000000000000000000000000000000..4347044a74eb5a3533092a1252ad830da37917da --- /dev/null +++ scroll-reth/crates/scroll/node/src/storage.rs @@ -0,0 +1,5 @@ +use reth_provider::EthStorage; +use reth_scroll_primitives::ScrollTransactionSigned; + +/// The storage implementation for Scroll. +pub type ScrollStorage = EthStorage<ScrollTransactionSigned>;
diff --git reth/crates/scroll/node/src/test_utils.rs scroll-reth/crates/scroll/node/src/test_utils.rs new file mode 100644 index 0000000000000000000000000000000000000000..be464870cbf42f151fc04a3a1175c3af19d9a7e2 --- /dev/null +++ scroll-reth/crates/scroll/node/src/test_utils.rs @@ -0,0 +1,87 @@ +use crate::{ScrollBuiltPayload, ScrollNode as OtherScrollNode, ScrollPayloadBuilderAttributes}; +use alloy_genesis::Genesis; +use alloy_primitives::{Address, B256}; +use alloy_rpc_types_engine::PayloadAttributes; +use reth_e2e_test_utils::{ + transaction::TransactionTestContext, wallet::Wallet, NodeHelperType, TmpDB, +}; +use reth_node_api::NodeTypesWithDBAdapter; + +use reth_payload_builder::EthPayloadBuilderAttributes; +use reth_provider::providers::BlockchainProvider; +use reth_scroll_chainspec::{ScrollChainConfig, ScrollChainSpecBuilder}; +use reth_tasks::TaskManager; +use scroll_alloy_rpc_types_engine::BlockDataHint; +use std::sync::Arc; +use tokio::sync::Mutex; + +/// Scroll Node Helper type +pub(crate) type ScrollNode = NodeHelperType< + OtherScrollNode, + BlockchainProvider<NodeTypesWithDBAdapter<OtherScrollNode, TmpDB>>, +>; + +/// Creates the initial setup with `num_nodes` of the node config, started and connected. +pub async fn setup( + num_nodes: usize, + is_dev: bool, +) -> eyre::Result<(Vec<ScrollNode>, TaskManager, Wallet)> { + let genesis: Genesis = + serde_json::from_str(include_str!("../tests/assets/genesis.json")).unwrap(); + reth_e2e_test_utils::setup_engine( + num_nodes, + Arc::new( + ScrollChainSpecBuilder::scroll_mainnet().genesis(genesis).euclid_v2_activated().build( + ScrollChainConfig { + max_tx_payload_bytes_per_block: 120 * 1024, + ..Default::default() + }, + ), + ), + is_dev, + Default::default(), + scroll_payload_attributes, + ) + .await +} + +/// Advance the chain with sequential payloads returning them in the end. +pub async fn advance_chain( + length: usize, + node: &mut ScrollNode, + wallet: Arc<Mutex<Wallet>>, +) -> eyre::Result<Vec<ScrollBuiltPayload>> { + node.advance(length as u64, |_| { + let wallet = wallet.clone(); + Box::pin(async move { + let mut wallet = wallet.lock().await; + let tx_fut = TransactionTestContext::transfer_tx_nonce_bytes( + wallet.chain_id, + wallet.inner.clone(), + wallet.inner_nonce, + ); + wallet.inner_nonce += 1; + tx_fut.await + }) + }) + .await +} + +/// Helper function to create a new scroll payload attributes +pub fn scroll_payload_attributes(timestamp: u64) -> ScrollPayloadBuilderAttributes { + let attributes = PayloadAttributes { + timestamp, + prev_randao: B256::ZERO, + suggested_fee_recipient: Address::ZERO, + withdrawals: None, + parent_beacon_block_root: Some(B256::ZERO), + }; + + ScrollPayloadBuilderAttributes { + payload_attributes: EthPayloadBuilderAttributes::new(B256::ZERO, attributes), + transactions: vec![], + no_tx_pool: false, + block_data_hint: BlockDataHint::none(), + gas_limit: None, + } +}
diff --git reth/crates/scroll/node/tests/assets/genesis.json scroll-reth/crates/scroll/node/tests/assets/genesis.json new file mode 100644 index 0000000000000000000000000000000000000000..6f1737c9a93156f4207621047723f02b2c7937e8 --- /dev/null +++ scroll-reth/crates/scroll/node/tests/assets/genesis.json @@ -0,0 +1,108 @@ +{ + "config": { + "chainId": 8453, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "arrowGlacierBlock": 0, + "grayGlacierBlock": 0, + "mergeNetsplitBlock": 0, + "bedrockBlock": 0, + "regolithTime": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x00", + "gasLimit": "0x1c9c380", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "0x14dc79964da2c08b23698b3d3cc7ca32193d9955": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x1cbd3b2770909d4e10f157cabc84c7264073c9ec": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x2546bcd3c84621e976d8185a91a922ae77ecec30": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x71be63f3384f5fb98995898a86b02fb2426c5788": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x8626f6940e2eb28930efb4cef49b2d1f2c9c1199": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x90f79bf6eb2c4f870365e785982e1f101e93b906": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x976ea74026e726554db657fa54763abd0c3a0aa9": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x9c41de96b2088cdc640c6182dfcf5491dc574a57": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xa0ee7a142d267c1f36714e4a8f75612f20a79720": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xbcd4042de499d14e55001ccbb24a551f3b954096": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xbda5747bfd65f08deb54cb465eb87d40e51b197e": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xcd3b766ccdd6ae721141f452c550ca635964ce71": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xdd2fd4581271e230360230f9337d5c0430bf44c0": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xdf3e18d64bc6a983f673ab319ccae4f1a57c7097": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xfabb0ac9d68b0b445fb7357272ff202c5651694a": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x5300000000000000000000000000000000000002": { + "balance": "0xd3c21bcecceda1000000", + "storage": { + "0x01": "0x000000000000000000000000000000000000000000000000000000003758e6b0", + "0x02": "0x0000000000000000000000000000000000000000000000000000000000000038", + "0x03": "0x000000000000000000000000000000000000000000000000000000003e95ba80", + "0x04": "0x0000000000000000000000005300000000000000000000000000000000000003", + "0x05": "0x000000000000000000000000000000000000000000000000000000008390c2c1", + "0x06": "0x00000000000000000000000000000000000000000000000000000069cf265bfe", + "0x07": "0x00000000000000000000000000000000000000000000000000000000168b9aa3" + } + } + }, + "number": "0x0" +} \ No newline at end of file
diff --git reth/crates/scroll/node/tests/e2e/main.rs scroll-reth/crates/scroll/node/tests/e2e/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..1402b98661a6946e2c62d2d11a158468546f98e5 --- /dev/null +++ scroll-reth/crates/scroll/node/tests/e2e/main.rs @@ -0,0 +1,3 @@ +#![allow(missing_docs)] + +mod payload;
diff --git reth/crates/scroll/node/tests/e2e/payload.rs scroll-reth/crates/scroll/node/tests/e2e/payload.rs new file mode 100644 index 0000000000000000000000000000000000000000..81e2bf4b45ad99460863aaf84ccfb2abd4ebcc68 --- /dev/null +++ scroll-reth/crates/scroll/node/tests/e2e/payload.rs @@ -0,0 +1,19 @@ +use reth_scroll_node::test_utils::{advance_chain, setup}; +use std::sync::Arc; +use tokio::sync::Mutex; + +#[tokio::test] +async fn can_sync() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + let (mut node, _tasks, wallet) = setup(1, false).await?; + let mut node = node.pop().unwrap(); + let wallet = Arc::new(Mutex::new(wallet)); + + let tip: usize = 90; + + // Create a chain of 90 blocks + let _canonical_payload_chain = advance_chain(tip, &mut node, wallet.clone()).await?; + + Ok(()) +}
diff --git reth/crates/scroll/openvm-compat/Cargo.lock scroll-reth/crates/scroll/openvm-compat/Cargo.lock new file mode 100644 index 0000000000000000000000000000000000000000..92cac3d6642117bae66c74dc5084fd9fc64ce799 --- /dev/null +++ scroll-reth/crates/scroll/openvm-compat/Cargo.lock @@ -0,0 +1,4127 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "alloy-chains" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5674914c2cfdb866c21cb0c09d82374ee39a1395cf512e7515f4c014083b3fff" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "num_enum", + "serde", + "strum", +] + +[[package]] +name = "alloy-consensus" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6093bc69509849435a2d68237a2e9fea79d27390c8e62f1e4012c460aabad8" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-trie", + "alloy-tx-macros", + "auto_impl", + "c-kzg", + "derive_more", + "either", + "k256", + "once_cell", + "rand 0.8.5", + "secp256k1", + "serde", + "serde_with", + "thiserror", +] + +[[package]] +name = "alloy-consensus-any" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d1cfed4fefd13b5620cb81cdb6ba397866ff0de514c1b24806e6e79cdff5570" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-eip2124" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "crc", + "serde", + "thiserror", +] + +[[package]] +name = "alloy-eip2930" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b82752a889170df67bbb36d42ca63c531eb16274f0d7299ae2a680facba17bd" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d4769c6ffddca380b0070d71c8b7f30bed375543fe76bb2f74ec0acf4b7cd16" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "k256", + "serde", + "serde_with", + "thiserror", +] + +[[package]] +name = "alloy-eips" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5937e2d544e9b71000942d875cbc57965b32859a666ea543cc57aae5a06d602d" +dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "auto_impl", + "c-kzg", + "derive_more", + "either", + "serde", + "sha2", +] + +[[package]] +name = "alloy-evm" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "822fc12d28a75059f87ef03939679e775c0655e83c98589500f7b9ec41d63e95" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-hardforks", + "alloy-primitives", + "alloy-sol-types", + "auto_impl", + "derive_more", + "revm", + "thiserror", +] + +[[package]] +name = "alloy-genesis" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51b4c13e02a8104170a4de02ccf006d7c233e6c10ab290ee16e7041e6ac221d" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "alloy-trie", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-hardforks" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819a3620fe125e0fff365363315ee5e24c23169173b19747dfd6deba33db8990" +dependencies = [ + "alloy-chains", + "alloy-eip2124", + "alloy-primitives", + "auto_impl", + "dyn-clone", +] + +[[package]] +name = "alloy-network-primitives" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793df1e3457573877fbde8872e4906638fde565ee2d3bd16d04aad17d43dbf0e" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-primitives" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cfebde8c581a5d37b678d0a48a32decb51efd7a63a08ce2517ddec26db705c8" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "foldhash", + "hashbrown 0.15.4", + "indexmap 2.10.0", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.9.1", + "ruint", + "rustc-hash", + "serde", + "sha3", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" +dependencies = [ + "alloy-rlp-derive", + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "alloy-rpc-types-engine" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f9cbf5f781b9ee39cfdddea078fdef6015424f4c8282ef0e5416d15ca352c4" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "derive_more", + "strum", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46586ec3c278639fc0e129f0eb73dbfa3d57f683c44b2ff5e066fab7ba63fa1f" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "itertools 0.14.0", + "serde", + "serde_json", + "serde_with", + "thiserror", +] + +[[package]] +name = "alloy-serde" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e1722bc30feef87cc0fa824e43c9013f9639cc6c037be7be28a31361c788be2" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-sol-macro" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedac07a10d4c2027817a43cc1f038313fc53c7ac866f7363239971fd01f9f18" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24f9a598f010f048d8b8226492b6401104f5a5c1273c2869b72af29b48bb4ba9" +dependencies = [ + "alloy-sol-macro-input", + "const-hex", + "heck", + "indexmap 2.10.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.104", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f494adf9d60e49aa6ce26dfd42c7417aa6d4343cf2ae621f20e4d92a5ad07d85" +dependencies = [ + "const-hex", + "dunce", + "heck", + "macro-string", + "proc-macro2", + "quote", + "syn 2.0.104", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-types" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a285b46e3e0c177887028278f04cc8262b76fd3b8e0e20e93cea0a58c35f5ac5" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", +] + +[[package]] +name = "alloy-trie" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bada1fc392a33665de0dc50d401a3701b62583c655e3522a323490a5da016962" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arrayvec", + "derive_more", + "nybbles", + "serde", + "smallvec", + "tracing", +] + +[[package]] +name = "alloy-tx-macros" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f916ff6d52f219c44a9684aea764ce2c7e1d53bd4a724c9b127863aeacc30bb" +dependencies = [ + "alloy-primitives", + "darling", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ark-bls12-381" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-poly", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.4", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.104", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.4", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] + +[[package]] +name = "aurora-engine-modexp" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518bc5745a6264b5fd7b09dffb9667e400ee9e2bbe18555fac75e1fe9afa0df9" +dependencies = [ + "hex", + "num", +] + +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitcoin-io" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" + +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +dependencies = [ + "serde", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "serde", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blst" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + +[[package]] +name = "bytemuck" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +dependencies = [ + "serde", +] + +[[package]] +name = "c-kzg" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7318cfa722931cb5fe0838b98d3ce5621e75f6a6408abc21721d80de9223f2e4" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] + +[[package]] +name = "cc" +version = "1.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "const-hex" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e22e0ed40b96a48d3db274f72fd365bd78f67af39b6bbd47e8a15e1c6207ff" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "proptest", + "serde", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_format" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.104", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive-where" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 2.0.104", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "serdect", + "signature", + "spki", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "allocator-api2", + "foldhash", + "serde", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown 0.15.4", + "serde", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "serdect", + "sha2", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "nybbles" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675b3a54e5b12af997abc8b6638b0aee51a28caedab70d4967e0d5db3a3f1d06" +dependencies = [ + "alloy-rlp", + "cfg-if", + "proptest", + "ruint", + "serde", + "smallvec", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "op-alloy-consensus" +version = "0.18.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3c719b26da6d9cac18c3a35634d6ab27a74a304ed9b403b43749c22e57a389f" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "derive_more", + "serde", + "serde_with", + "thiserror", +] + +[[package]] +name = "openvm" +version = "1.2.0" +source = "git+https://github.com/openvm-org/openvm.git?tag=v1.2.0#bdb4831fefed13b0741d3a052d434a9c995c6d5d" +dependencies = [ + "bytemuck", + "num-bigint", + "openvm-custom-insn", + "openvm-platform", + "openvm-rv32im-guest", + "serde", +] + +[[package]] +name = "openvm-compat" +version = "0.0.1" +dependencies = [ + "openvm", + "reth-chainspec", + "reth-ethereum-forks", + "reth-evm", + "reth-evm-ethereum", + "reth-execution-types", + "reth-primitives", + "reth-primitives-traits", + "reth-scroll-chainspec", + "reth-scroll-evm", + "reth-scroll-forks", + "reth-scroll-primitives", + "reth-storage-errors", + "reth-trie", + "reth-trie-sparse", + "scroll-alloy-consensus", + "scroll-alloy-rpc-types", +] + +[[package]] +name = "openvm-custom-insn" +version = "0.1.0" +source = "git+https://github.com/openvm-org/openvm.git?tag=v1.2.0#bdb4831fefed13b0741d3a052d434a9c995c6d5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "openvm-platform" +version = "1.2.0" +source = "git+https://github.com/openvm-org/openvm.git?tag=v1.2.0#bdb4831fefed13b0741d3a052d434a9c995c6d5d" +dependencies = [ + "getrandom 0.2.16", + "libm", + "openvm-custom-insn", + "openvm-rv32im-guest", +] + +[[package]] +name = "openvm-rv32im-guest" +version = "1.2.0" +source = "git+https://github.com/openvm-org/openvm.git?tag=v1.2.0#bdb4831fefed13b0741d3a052d434a9c995c6d5d" +dependencies = [ + "openvm-custom-insn", + "strum_macros 0.26.4", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "lazy_static", + "num-traits", + "rand 0.9.1", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "serde", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", + "serde", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.3", +] + +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reth-chainspec" +version = "1.6.0" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-genesis", + "alloy-primitives", + "alloy-trie", + "auto_impl", + "derive_more", + "reth-ethereum-forks", + "reth-network-peers", + "reth-primitives-traits", + "serde_json", +] + +[[package]] +name = "reth-codecs" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-trie", + "bytes", + "modular-bitfield", + "op-alloy-consensus", + "reth-codecs-derive", + "reth-zstd-compressors", + "serde", +] + +[[package]] +name = "reth-codecs-derive" +version = "1.6.0" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "reth-db-models" +version = "1.6.0" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "reth-primitives-traits", +] + +[[package]] +name = "reth-ethereum-forks" +version = "1.6.0" +dependencies = [ + "alloy-eip2124", + "alloy-hardforks", + "alloy-primitives", + "auto_impl", + "once_cell", +] + +[[package]] +name = "reth-ethereum-primitives" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "reth-codecs", + "reth-primitives-traits", + "serde", + "serde_with", +] + +[[package]] +name = "reth-evm" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-primitives", + "auto_impl", + "derive_more", + "futures-util", + "reth-execution-errors", + "reth-execution-types", + "reth-primitives-traits", + "reth-storage-api", + "reth-storage-errors", + "reth-trie-common", + "revm", + "scroll-alloy-evm", +] + +[[package]] +name = "reth-evm-ethereum" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-primitives", + "reth-chainspec", + "reth-ethereum-forks", + "reth-ethereum-primitives", + "reth-evm", + "reth-execution-types", + "reth-primitives-traits", + "revm", +] + +[[package]] +name = "reth-execution-errors" +version = "1.6.0" +dependencies = [ + "alloy-evm", + "alloy-primitives", + "alloy-rlp", + "nybbles", + "reth-storage-errors", + "thiserror", +] + +[[package]] +name = "reth-execution-types" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-primitives", + "derive_more", + "reth-ethereum-primitives", + "reth-primitives-traits", + "reth-trie-common", + "revm", +] + +[[package]] +name = "reth-network-peers" +version = "1.6.0" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "serde_with", + "thiserror", + "url", +] + +[[package]] +name = "reth-primitives" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "once_cell", + "reth-ethereum-forks", + "reth-ethereum-primitives", + "reth-primitives-traits", + "reth-static-file-types", +] + +[[package]] +name = "reth-primitives-traits" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-trie", + "auto_impl", + "bytes", + "derive_more", + "once_cell", + "op-alloy-consensus", + "reth-codecs", + "revm-bytecode", + "revm-primitives", + "revm-state", + "scroll-alloy-consensus", + "secp256k1", + "serde", + "serde_with", + "thiserror", +] + +[[package]] +name = "reth-prune-types" +version = "1.6.0" +dependencies = [ + "alloy-primitives", + "derive_more", + "thiserror", +] + +[[package]] +name = "reth-scroll-chainspec" +version = "1.6.0" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-serde", + "auto_impl", + "derive_more", + "once_cell", + "reth-chainspec", + "reth-ethereum-forks", + "reth-network-peers", + "reth-primitives-traits", + "reth-scroll-forks", + "reth-trie-common", + "scroll-alloy-hardforks", + "serde", + "serde_json", +] + +[[package]] +name = "reth-scroll-evm" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-primitives", + "derive_more", + "reth-chainspec", + "reth-evm", + "reth-execution-types", + "reth-primitives", + "reth-primitives-traits", + "reth-scroll-chainspec", + "reth-scroll-forks", + "reth-scroll-primitives", + "reth-storage-api", + "revm", + "revm-primitives", + "revm-scroll", + "scroll-alloy-consensus", + "scroll-alloy-evm", + "scroll-alloy-hardforks", + "thiserror", + "tracing", +] + +[[package]] +name = "reth-scroll-forks" +version = "1.6.0" +dependencies = [ + "alloy-chains", + "alloy-primitives", + "auto_impl", + "once_cell", + "reth-ethereum-forks", + "scroll-alloy-hardforks", +] + +[[package]] +name = "reth-scroll-primitives" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "bytes", + "once_cell", + "reth-codecs", + "reth-primitives-traits", + "scroll-alloy-consensus", + "serde", +] + +[[package]] +name = "reth-stages-types" +version = "1.6.0" +dependencies = [ + "alloy-primitives", + "reth-trie-common", +] + +[[package]] +name = "reth-static-file-types" +version = "1.6.0" +dependencies = [ + "alloy-primitives", + "derive_more", + "serde", + "strum", +] + +[[package]] +name = "reth-storage-api" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rpc-types-engine", + "auto_impl", + "reth-chainspec", + "reth-db-models", + "reth-ethereum-primitives", + "reth-execution-types", + "reth-primitives-traits", + "reth-prune-types", + "reth-stages-types", + "reth-storage-errors", + "reth-trie-common", + "revm-database", +] + +[[package]] +name = "reth-storage-errors" +version = "1.6.0" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "derive_more", + "reth-primitives-traits", + "reth-prune-types", + "reth-static-file-types", + "revm-database-interface", + "thiserror", +] + +[[package]] +name = "reth-trie" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-trie", + "auto_impl", + "itertools 0.14.0", + "reth-execution-errors", + "reth-primitives-traits", + "reth-stages-types", + "reth-storage-errors", + "reth-trie-common", + "reth-trie-sparse", + "revm-database", + "tracing", +] + +[[package]] +name = "reth-trie-common" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rlp", + "alloy-trie", + "derive_more", + "itertools 0.14.0", + "nybbles", + "reth-primitives-traits", + "revm-database", +] + +[[package]] +name = "reth-trie-sparse" +version = "1.6.0" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-trie", + "auto_impl", + "reth-execution-errors", + "reth-primitives-traits", + "reth-trie-common", + "smallvec", + "tracing", +] + +[[package]] +name = "reth-zstd-compressors" +version = "1.6.0" +dependencies = [ + "zstd", +] + +[[package]] +name = "revm" +version = "27.1.0" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" +dependencies = [ + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database", + "revm-database-interface", + "revm-handler", + "revm-inspector", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-bytecode" +version = "6.1.0" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" +dependencies = [ + "bitvec", + "once_cell", + "phf", + "revm-primitives", + "serde", +] + +[[package]] +name = "revm-context" +version = "8.0.4" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" +dependencies = [ + "cfg-if", + "derive-where", + "revm-bytecode", + "revm-context-interface", + "revm-database-interface", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-context-interface" +version = "9.0.0" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "auto_impl", + "either", + "revm-database-interface", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-database" +version = "7.0.2" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" +dependencies = [ + "revm-bytecode", + "revm-database-interface", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-database-interface" +version = "7.0.2" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" +dependencies = [ + "auto_impl", + "either", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-handler" +version = "8.1.0" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" +dependencies = [ + "auto_impl", + "derive-where", + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database-interface", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-inspector" +version = "8.1.0" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" +dependencies = [ + "auto_impl", + "either", + "revm-context", + "revm-database-interface", + "revm-handler", + "revm-interpreter", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-interpreter" +version = "24.0.0" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" +dependencies = [ + "revm-bytecode", + "revm-context-interface", + "revm-primitives", +] + +[[package]] +name = "revm-precompile" +version = "25.0.0" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" +dependencies = [ + "ark-bls12-381", + "ark-bn254", + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "arrayref", + "aurora-engine-modexp", + "cfg-if", + "k256", + "once_cell", + "p256", + "revm-primitives", + "ripemd", + "sha2", +] + +[[package]] +name = "revm-primitives" +version = "20.1.0" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" +dependencies = [ + "alloy-primitives", + "num_enum", + "serde", +] + +[[package]] +name = "revm-scroll" +version = "0.1.0" +source = "git+https://github.com/scroll-tech/scroll-revm#08e6281f419517527d77a66fae19075e8bfeac3e" +dependencies = [ + "auto_impl", + "enumn", + "once_cell", + "revm", + "revm-inspector", + "revm-primitives", +] + +[[package]] +name = "revm-state" +version = "7.0.2" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" +dependencies = [ + "bitflags", + "revm-bytecode", + "revm-primitives", + "serde", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "ruint" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11256b5fe8c68f56ac6f39ef0720e592f33d2367a4782740d9c9142e889c7fb4" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand 0.8.5", + "rand 0.9.1", + "rlp", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.26", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "scroll-alloy-consensus" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "derive_more", + "reth-codecs", + "serde", + "serde_with", +] + +[[package]] +name = "scroll-alloy-evm" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-primitives", + "auto_impl", + "revm", + "revm-scroll", + "scroll-alloy-consensus", + "scroll-alloy-hardforks", +] + +[[package]] +name = "scroll-alloy-hardforks" +version = "1.6.0" +dependencies = [ + "alloy-hardforks", + "auto_impl", +] + +[[package]] +name = "scroll-alloy-rpc-types" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "derive_more", + "scroll-alloy-consensus", + "serde", + "serde_json", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.10.0", + "schemars 0.9.0", + "schemars 1.0.4", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sha3-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +dependencies = [ + "cc", + "cfg-if", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" +dependencies = [ + "strum_macros 0.27.1", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.104", +] + +[[package]] +name = "strum_macros" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.104", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a985ff4ffd7373e10e0fb048110fb11a162e5a4c47f92ddb8787a6f766b769" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.10.0", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.104", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +]
diff --git reth/crates/scroll/openvm-compat/Cargo.toml scroll-reth/crates/scroll/openvm-compat/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..e244598819b122fa45640a982db8099ff01c2ff3 --- /dev/null +++ scroll-reth/crates/scroll/openvm-compat/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "openvm-compat" +version = "0.0.1" +edition = "2024" + +[workspace] + +[dependencies] +openvm = { git = "https://github.com/openvm-org/openvm.git", tag = "v1.2.0", default-features = false } + +reth-chainspec = { path = "../../chainspec", default-features = false } +reth-evm = { path = "../../evm/evm", default-features = false } +reth-evm-ethereum = { path = "../../ethereum/evm", default-features = false } +reth-ethereum-forks = { path = "../../ethereum/hardforks", default-features = false } +reth-execution-types = { path = "../../evm/execution-types", default-features = false } +reth-primitives = { path = "../../primitives", default-features = false } +reth-primitives-traits = { path = "../../primitives-traits", default-features = false } +reth-storage-errors = { path = "../../storage/errors", default-features = false } +reth-trie = { path = "../../trie/trie", default-features = false } +reth-trie-sparse = { path = "../../trie/sparse", default-features = false } + +reth-scroll-chainspec = { path = "../chainspec", default-features = false } +reth-scroll-evm = { path = "../evm", default-features = false } +reth-scroll-forks = { path = "../hardforks", default-features = false } +reth-scroll-primitives = { path = "../primitives", default-features = false } + +scroll-alloy-consensus = { path = "../alloy/consensus", default-features = false } +scroll-alloy-rpc-types = { path = "../alloy/rpc-types", default-features = false } + +[patch.crates-io] +revm = { git = "https://github.com/scroll-tech/revm" }
diff --git reth/crates/scroll/openvm-compat/src/main.rs scroll-reth/crates/scroll/openvm-compat/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..8fd9e8ae9c79267a45b20339a75ce2ed5de8f199 --- /dev/null +++ scroll-reth/crates/scroll/openvm-compat/src/main.rs @@ -0,0 +1,9 @@ +//! `OpenVM` compatibility bin. + +#![no_main] +#![no_std] + +openvm::entry!(main); + +#[allow(dead_code)] +const fn main() {}
diff --git reth/crates/scroll/payload/Cargo.toml scroll-reth/crates/scroll/payload/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..c3eeb840f0be4f378f91a19acde2ea1c0ad74348 --- /dev/null +++ scroll-reth/crates/scroll/payload/Cargo.toml @@ -0,0 +1,61 @@ +[package] +name = "reth-scroll-payload" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# alloy +alloy-rlp.workspace = true +alloy-consensus.workspace = true +alloy-primitives.workspace = true + +# scroll-alloy +reth-scroll-evm.workspace = true +scroll-alloy-hardforks.workspace = true + +# revm +revm.workspace = true + +# reth +reth-basic-payload-builder.workspace = true +reth-chainspec.workspace = true +reth-chain-state.workspace = true +reth-evm.workspace = true +reth-execution-types.workspace = true +reth-payload-builder.workspace = true +reth-payload-primitives.workspace = true +reth-primitives-traits.workspace = true +reth-revm.workspace = true +reth-storage-api.workspace = true +reth-transaction-pool.workspace = true +reth-payload-util.workspace = true + +# scroll +reth-scroll-chainspec.workspace = true +reth-scroll-primitives.workspace = true +reth-scroll-engine-primitives.workspace = true + +# misc +futures-util = { workspace = true, optional = true } +thiserror.workspace = true +tracing.workspace = true + +[features] +test-utils = [ + "dep:futures-util", + "reth-payload-builder/test-utils", + "reth-primitives-traits/test-utils", + "reth-transaction-pool/test-utils", + "reth-chain-state/test-utils", + "reth-chainspec/test-utils", + "reth-evm/test-utils", + "reth-revm/test-utils", +]
diff --git reth/crates/scroll/payload/src/builder.rs scroll-reth/crates/scroll/payload/src/builder.rs new file mode 100644 index 0000000000000000000000000000000000000000..d0bf505d0299d4a4baf18e1ea40756c2044354d2 --- /dev/null +++ scroll-reth/crates/scroll/payload/src/builder.rs @@ -0,0 +1,608 @@ +//! Scroll's payload builder implementation. + +use super::ScrollPayloadBuilderError; +use crate::config::{PayloadBuildingBreaker, ScrollBuilderConfig}; + +use alloy_consensus::{Transaction, Typed2718}; +use alloy_primitives::{B256, U256}; +use alloy_rlp::Encodable; +use core::fmt::Debug; +use reth_basic_payload_builder::{ + is_better_payload, BuildArguments, BuildOutcome, BuildOutcomeKind, MissingPayloadBehaviour, + PayloadBuilder, PayloadConfig, +}; +use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates}; +use reth_chainspec::{ChainSpecProvider, EthChainSpec}; +use reth_evm::{ + block::{BlockExecutionError, BlockValidationError}, + execute::{BlockBuilder, BlockBuilderOutcome, ProviderError}, + ConfigureEvm, Database, Evm, +}; +use reth_execution_types::ExecutionOutcome; +use reth_payload_builder::PayloadId; +use reth_payload_primitives::{PayloadBuilderAttributes, PayloadBuilderError}; +use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; +use reth_primitives_traits::{ + NodePrimitives, RecoveredBlock, SealedHeader, SignedTransaction, TxTy, +}; +use reth_revm::{cancelled::CancelOnDrop, database::StateProviderDatabase, db::State}; +use reth_scroll_chainspec::{ChainConfig, ScrollChainConfig}; +use reth_scroll_engine_primitives::{ScrollBuiltPayload, ScrollPayloadBuilderAttributes}; +use reth_scroll_evm::{ScrollBaseFeeProvider, ScrollNextBlockEnvAttributes}; +use reth_scroll_primitives::{ScrollPrimitives, ScrollTransactionSigned}; +use reth_storage_api::{BaseFeeProvider, StateProvider, StateProviderFactory}; +use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; +use revm::context::{Block, BlockEnv}; +use scroll_alloy_hardforks::ScrollHardforks; +use std::{boxed::Box, sync::Arc, vec, vec::Vec}; + +/// A type that returns the [`PayloadTransactions`] that should be included in the pool. +pub trait ScrollPayloadTransactions<Transaction>: Clone + Send + Sync + Unpin + 'static { + /// Returns an iterator that yields the transaction in the order they should get included in the + /// new payload. + fn best_transactions<Pool: TransactionPool<Transaction = Transaction>>( + &self, + pool: Pool, + attr: BestTransactionsAttributes, + ) -> impl PayloadTransactions<Transaction = Transaction>; +} + +impl<T: PoolTransaction> ScrollPayloadTransactions<T> for () { + fn best_transactions<Pool: TransactionPool<Transaction = T>>( + &self, + pool: Pool, + attr: BestTransactionsAttributes, + ) -> impl PayloadTransactions<Transaction = T> { + BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr)) + } +} + +/// Scroll's payload builder. +#[derive(Clone, Debug)] +pub struct ScrollPayloadBuilder<Pool, Client, Evm, Txs = ()> { + /// The type responsible for creating the evm. + pub evm_config: Evm, + /// Transaction pool. + pub pool: Pool, + /// Node client + pub client: Client, + /// The type responsible for yielding the best transactions to include in a payload. + pub best_transactions: Txs, + /// Payload builder configuration. + pub builder_config: ScrollBuilderConfig, +} + +impl<Pool, Evm, Client> ScrollPayloadBuilder<Pool, Client, Evm> { + /// Creates a new [`ScrollPayloadBuilder`]. + pub const fn new( + pool: Pool, + evm_config: Evm, + client: Client, + builder_config: ScrollBuilderConfig, + ) -> Self { + Self { evm_config, pool, client, best_transactions: (), builder_config } + } +} + +impl<Pool, Client, Evm, Txs> ScrollPayloadBuilder<Pool, Client, Evm, Txs> { + /// Configures the type responsible for yielding the transactions that should be included in the + /// payload. + pub fn with_transactions<T>( + self, + best_transactions: T, + ) -> ScrollPayloadBuilder<Pool, Client, Evm, T> { + let Self { evm_config, pool, client, builder_config, .. } = self; + ScrollPayloadBuilder { evm_config, pool, client, best_transactions, builder_config } + } +} + +impl<Pool, Client, Evm, T> ScrollPayloadBuilder<Pool, Client, Evm, T> +where + Pool: TransactionPool<Transaction: PoolTransaction<Consensus = ScrollTransactionSigned>>, + Client: StateProviderFactory + + ChainSpecProvider< + ChainSpec: EthChainSpec + + ScrollHardforks + + ChainConfig<Config = ScrollChainConfig> + + Clone, + >, + Evm: + ConfigureEvm<Primitives = ScrollPrimitives, NextBlockEnvCtx = ScrollNextBlockEnvAttributes>, +{ + /// Constructs a Scroll payload from the transactions sent via the + /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in + /// the payload attributes, the transaction pool will be ignored and the only transactions + /// included in the payload will be those sent through the attributes. + /// + /// Given build arguments including a Scroll client, transaction pool, + /// and configuration, this function creates a transaction payload. Returns + /// a result indicating success with the payload or an error in case of failure. + fn build_payload<'a, Txs>( + &self, + args: BuildArguments<ScrollPayloadBuilderAttributes, ScrollBuiltPayload>, + best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a, + ) -> Result<BuildOutcome<ScrollBuiltPayload>, PayloadBuilderError> + where + Txs: PayloadTransactions<Transaction: PoolTransaction<Consensus = ScrollTransactionSigned>>, + { + let BuildArguments { mut cached_reads, config, cancel, best_payload } = args; + + let ctx = ScrollPayloadBuilderCtx { + evm_config: self.evm_config.clone(), + chain_spec: self.client.chain_spec(), + config, + cancel, + best_payload, + }; + + let builder = ScrollBuilder::new(best); + + let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; + let state = StateProviderDatabase::new(&state_provider); + + if ctx.attributes().no_tx_pool { + builder.build(state, &state_provider, ctx, &self.builder_config) + } else { + // sequencer mode we can reuse cachedreads from previous runs + builder.build(cached_reads.as_db_mut(state), &state_provider, ctx, &self.builder_config) + } + .map(|out| out.with_cached_reads(cached_reads)) + } +} + +/// Implementation of the [`PayloadBuilder`] trait for [`ScrollPayloadBuilder`]. +impl<Pool, Client, Evm, Txs> PayloadBuilder for ScrollPayloadBuilder<Pool, Client, Evm, Txs> +where + Client: StateProviderFactory + + ChainSpecProvider< + ChainSpec: EthChainSpec + + ScrollHardforks + + ChainConfig<Config = ScrollChainConfig> + + Clone, + > + Clone, + Pool: TransactionPool<Transaction: PoolTransaction<Consensus = ScrollTransactionSigned>>, + Evm: + ConfigureEvm<Primitives = ScrollPrimitives, NextBlockEnvCtx = ScrollNextBlockEnvAttributes>, + Txs: ScrollPayloadTransactions<Pool::Transaction>, +{ + type Attributes = ScrollPayloadBuilderAttributes; + type BuiltPayload = ScrollBuiltPayload; + + fn try_build( + &self, + args: BuildArguments<Self::Attributes, Self::BuiltPayload>, + ) -> Result<BuildOutcome<Self::BuiltPayload>, PayloadBuilderError> { + let pool = self.pool.clone(); + self.build_payload(args, |attrs| self.best_transactions.best_transactions(pool, attrs)) + } + + fn on_missing_payload( + &self, + _args: BuildArguments<Self::Attributes, Self::BuiltPayload>, + ) -> MissingPayloadBehaviour<Self::BuiltPayload> { + // we want to await the job that's already in progress because that should be returned as + // is, there's no benefit in racing another job + MissingPayloadBehaviour::AwaitInProgress + } + + // NOTE: this should only be used for testing purposes because this doesn't have access to L1 + // system txs, hence on_missing_payload we return [MissingPayloadBehaviour::AwaitInProgress]. + fn build_empty_payload( + &self, + config: PayloadConfig<Self::Attributes>, + ) -> Result<Self::BuiltPayload, PayloadBuilderError> { + let args = BuildArguments { + config, + cached_reads: Default::default(), + cancel: Default::default(), + best_payload: None, + }; + self.build_payload(args, |_| NoopPayloadTransactions::<Pool::Transaction>::default())? + .into_payload() + .ok_or_else(|| PayloadBuilderError::MissingPayload) + } +} + +/// A builder for a new payload. +pub struct ScrollBuilder<'a, Txs> { + /// Yields the best transaction to include if transactions from the mempool are allowed. + best: Box<dyn FnOnce(BestTransactionsAttributes) -> Txs + 'a>, +} + +impl<'a, Txs> ScrollBuilder<'a, Txs> { + /// Creates a new [`ScrollBuilder`]. + pub fn new(best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a) -> Self { + Self { best: Box::new(best) } + } +} + +impl<'a, Txs> std::fmt::Debug for ScrollBuilder<'a, Txs> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ScrollBuilder").finish() + } +} + +impl<Txs> ScrollBuilder<'_, Txs> { + /// Builds the payload on top of the state. + pub fn build<EvmConfig, ChainSpec>( + self, + db: impl Database<Error = ProviderError>, + state_provider: impl StateProvider, + ctx: ScrollPayloadBuilderCtx<EvmConfig, ChainSpec>, + builder_config: &ScrollBuilderConfig, + ) -> Result<BuildOutcomeKind<ScrollBuiltPayload>, PayloadBuilderError> + where + EvmConfig: ConfigureEvm< + Primitives = ScrollPrimitives, + NextBlockEnvCtx = ScrollNextBlockEnvAttributes, + >, + ChainSpec: EthChainSpec + ScrollHardforks + ChainConfig<Config = ScrollChainConfig> + Clone, + Txs: PayloadTransactions<Transaction: PoolTransaction<Consensus = ScrollTransactionSigned>>, + { + let Self { best } = self; + tracing::debug!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload"); + let breaker = builder_config.breaker(); + + let mut db = State::builder().with_database(db).with_bundle_update().build(); + + let mut builder = ctx.block_builder(&mut db, builder_config)?; + + // 1. apply pre-execution changes + builder.apply_pre_execution_changes().map_err(|err| { + tracing::warn!(target: "payload_builder", %err, "failed to apply pre-execution changes"); + PayloadBuilderError::Internal(err.into()) + })?; + + // 2. execute sequencer transactions + let mut info = ctx.execute_sequencer_transactions(&mut builder)?; + + // 3. if mem pool transactions are requested we execute them + if !ctx.attributes().no_tx_pool { + let best_txs = best(ctx.best_transaction_attributes(builder.evm_mut().block())); + if ctx + .execute_best_transactions( + &mut info, + &mut builder, + best_txs, + builder_config, + breaker, + )? + .is_some() + { + return Ok(BuildOutcomeKind::Cancelled); + } + + // check if the new payload is even more valuable + if !ctx.is_better_payload(info.total_fees) { + // can skip building the block + return Ok(BuildOutcomeKind::Aborted { fees: info.total_fees }) + } + } + + let BlockBuilderOutcome { execution_result, hashed_state, trie_updates, mut block } = + builder.finish(state_provider)?; + + // set the block fields using the hints from the payload attributes. + let (mut scroll_block, senders) = block.split(); + scroll_block = scroll_block.map_header(|mut header| { + if let Some(extra_data) = &ctx.config.attributes.block_data_hint.extra_data { + header.extra_data = extra_data.clone(); + } + if let Some(state_root) = ctx.config.attributes.block_data_hint.state_root { + header.state_root = state_root; + } + if let Some(coinbase) = ctx.config.attributes.block_data_hint.coinbase { + header.beneficiary = coinbase; + } + if let Some(nonce) = ctx.config.attributes.block_data_hint.nonce { + header.nonce = nonce.into() + } + if let Some(difficulty) = ctx.config.attributes.block_data_hint.difficulty { + header.difficulty = difficulty; + } + header + }); + block = RecoveredBlock::new_unhashed(scroll_block, senders); + + let sealed_block = Arc::new(block.sealed_block().clone()); + tracing::debug!(target: "payload_builder", id=%ctx.attributes().payload_id(), sealed_block_header = ?sealed_block.header(), "sealed built block"); + + let execution_outcome = ExecutionOutcome::new( + db.take_bundle(), + vec![execution_result.receipts], + block.number, + Vec::new(), + ); + + // create the executed block data + let executed: ExecutedBlockWithTrieUpdates<ScrollPrimitives> = + ExecutedBlockWithTrieUpdates { + block: ExecutedBlock { + recovered_block: Arc::new(block), + execution_output: Arc::new(execution_outcome), + hashed_state: Arc::new(hashed_state), + }, + trie: ExecutedTrieUpdates::Present(Arc::new(trie_updates)), + }; + + let no_tx_pool = ctx.attributes().no_tx_pool; + + let payload = ScrollBuiltPayload::new( + ctx.payload_id(), + sealed_block, + Some(executed), + info.total_fees, + ); + + if no_tx_pool { + // if `no_tx_pool` is set only transactions from the payload attributes will be included + // in the payload. In other words, the payload is deterministic and we can + // freeze it once we've successfully built it. + Ok(BuildOutcomeKind::Freeze(payload)) + } else { + Ok(BuildOutcomeKind::Better { payload }) + } + } +} + +/// Container type that holds all necessities to build a new payload. +#[derive(Debug)] +pub struct ScrollPayloadBuilderCtx<Evm: ConfigureEvm, ChainSpec> { + /// The type that knows how to perform system calls and configure the evm. + pub evm_config: Evm, + /// The chainspec + pub chain_spec: ChainSpec, + /// How to build the payload. + pub config: PayloadConfig<ScrollPayloadBuilderAttributes>, + /// Marker to check whether the job has been cancelled. + pub cancel: CancelOnDrop, + /// The currently best payload. + pub best_payload: Option<ScrollBuiltPayload>, +} + +impl<Evm, ChainSpec> ScrollPayloadBuilderCtx<Evm, ChainSpec> +where + Evm: + ConfigureEvm<Primitives = ScrollPrimitives, NextBlockEnvCtx = ScrollNextBlockEnvAttributes>, + ChainSpec: EthChainSpec + ScrollHardforks + ChainConfig<Config = ScrollChainConfig> + Clone, +{ + /// Returns the parent block the payload will be build on. + #[allow(clippy::missing_const_for_fn)] + pub fn parent(&self) -> &SealedHeader { + &self.config.parent_header + } + + /// Returns the builder attributes. + pub const fn attributes(&self) -> &ScrollPayloadBuilderAttributes { + &self.config.attributes + } + + /// Returns the current fee settings for transactions from the mempool + pub fn best_transaction_attributes(&self, block_env: &BlockEnv) -> BestTransactionsAttributes { + BestTransactionsAttributes::new( + block_env.basefee, + block_env.blob_gasprice().map(|p| p as u64), + ) + } + + /// Returns the unique id for this payload job. + pub fn payload_id(&self) -> PayloadId { + self.attributes().payload_id() + } + + /// Returns true if the fees are higher than the previous payload. + pub fn is_better_payload(&self, total_fees: U256) -> bool { + is_better_payload(self.best_payload.as_ref(), total_fees) + } + + /// Prepares a [`BlockBuilder`] for the next block. + pub fn block_builder<'a, DB: Database>( + &'a self, + db: &'a mut State<DB>, + builder_config: &ScrollBuilderConfig, + ) -> Result<impl BlockBuilder<Primitives = Evm::Primitives> + 'a, PayloadBuilderError> { + // get the base fee for the attributes. + let base_fee_provider = ScrollBaseFeeProvider::new(self.chain_spec.clone()); + let base_fee: u64 = base_fee_provider + .next_block_base_fee(db, self.parent().header(), self.attributes().timestamp()) + .map_err(|err| PayloadBuilderError::Other(Box::new(err)))?; + + self.evm_config + .builder_for_next_block( + db, + self.parent(), + ScrollNextBlockEnvAttributes { + timestamp: self.attributes().timestamp(), + suggested_fee_recipient: self.attributes().suggested_fee_recipient(), + gas_limit: self + .attributes() + .gas_limit + .unwrap_or_else(|| builder_config.gas_limit.unwrap_or_default()), + base_fee, + }, + ) + .map_err(PayloadBuilderError::other) + } +} + +impl<Evm, ChainSpec> ScrollPayloadBuilderCtx<Evm, ChainSpec> +where + Evm: + ConfigureEvm<Primitives = ScrollPrimitives, NextBlockEnvCtx = ScrollNextBlockEnvAttributes>, + ChainSpec: EthChainSpec + ScrollHardforks + ChainConfig<Config = ScrollChainConfig> + Clone, +{ + /// Executes all sequencer transactions that are included in the payload attributes. + pub fn execute_sequencer_transactions( + &self, + builder: &mut impl BlockBuilder<Primitives = Evm::Primitives>, + ) -> Result<ExecutionInfo, PayloadBuilderError> { + let mut info = ExecutionInfo::new(); + + for sequencer_tx in &self.attributes().transactions { + // A sequencer's block should never contain blob transactions. + if sequencer_tx.value().is_eip4844() { + return Err(PayloadBuilderError::other( + ScrollPayloadBuilderError::BlobTransactionRejected, + )) + } + + // Convert the transaction to a [RecoveredTx]. This is + // purely for the purposes of utilizing the `evm_config.tx_env`` function. + // Deposit transactions do not have signatures, so if the tx is a deposit, this + // will just pull in its `from` address. + let sequencer_tx = sequencer_tx.value().try_clone_into_recovered().map_err(|_| { + PayloadBuilderError::other(ScrollPayloadBuilderError::TransactionEcRecoverFailed) + })?; + + let gas_used = match builder.execute_transaction(sequencer_tx.clone()) { + Ok(gas_used) => gas_used, + Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { + error, + .. + })) => { + tracing::trace!(target: "payload_builder", %error, ?sequencer_tx, "Error in sequencer transaction, skipping."); + continue + } + Err(err) => { + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))) + } + }; + + // add gas used by the transaction to cumulative gas used, before creating the receipt + info.cumulative_gas_used += gas_used; + } + + Ok(info) + } + + /// Executes the given best transactions and updates the execution info. + /// + /// Returns `Ok(Some(())` if the job was cancelled. + pub fn execute_best_transactions( + &self, + info: &mut ExecutionInfo, + builder: &mut impl BlockBuilder<Primitives = Evm::Primitives>, + mut best_txs: impl PayloadTransactions< + Transaction: PoolTransaction<Consensus = TxTy<Evm::Primitives>>, + >, + builder_config: &ScrollBuilderConfig, + breaker: PayloadBuildingBreaker, + ) -> Result<Option<()>, PayloadBuilderError> { + let block_gas_limit = builder.evm_mut().block().gas_limit; + let base_fee = builder.evm_mut().block().basefee; + + while let Some(tx) = best_txs.next(()) { + let tx = tx.into_consensus(); + if info.is_tx_over_limits(tx.inner(), block_gas_limit, builder_config.max_da_block_size) + { + // we can't fit this transaction into the block, so we need to mark it as + // invalid which also removes all dependent transaction from + // the iterator before we can continue + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue + } + + // A sequencer's block should never contain blob or deposit transactions from the pool. + if tx.is_eip4844() || tx.is_l1_message() { + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue + } + + // check if the job was cancelled, if so we can exit early + if self.cancel.is_cancelled() { + return Ok(Some(())) + } + + // check if the execution needs to be halted. + if breaker.should_break(info.cumulative_gas_used, info.cumulative_da_bytes_used) { + tracing::trace!(target: "scroll::payload_builder", ?info, "breaking execution loop"); + return Ok(None); + } + + let gas_used = match builder.execute_transaction(tx.clone()) { + Ok(gas_used) => gas_used, + Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { + error, + .. + })) => { + if error.is_nonce_too_low() { + // if the nonce is too low, we can skip this transaction + tracing::trace!(target: "payload_builder", %error, ?tx, "skipping nonce too low transaction"); + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + tracing::trace!(target: "payload_builder", %error, ?tx, "skipping invalid transaction and its descendants"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + } + continue + } + Err(err) => { + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))) + } + }; + + // add gas used by the transaction to cumulative gas used, before creating the + // receipt + info.cumulative_gas_used += gas_used; + info.cumulative_da_bytes_used += tx.length() as u64; + + // update add to total fees + let miner_fee = tx + .effective_tip_per_gas(base_fee) + .expect("fee is always valid; execution succeeded"); + info.total_fees += U256::from(miner_fee) * U256::from(gas_used); + } + + Ok(None) + } +} + +/// Holds the state after execution +#[derive(Debug)] +pub struct ExecutedPayload<N: NodePrimitives> { + /// Tracked execution info + pub info: ExecutionInfo, + /// Withdrawal hash. + pub withdrawals_root: Option<B256>, + /// The transaction receipts. + pub receipts: Vec<N::Receipt>, + /// The block env used during execution. + pub block_env: BlockEnv, +} + +/// This acts as the container for executed transactions and its byproducts (receipts, gas used) +#[derive(Default, Debug)] +pub struct ExecutionInfo { + /// All gas used so far + pub cumulative_gas_used: u64, + /// Estimated DA size + pub cumulative_da_bytes_used: u64, + /// Tracks fees from executed mempool transactions + pub total_fees: U256, +} + +impl ExecutionInfo { + /// Create a new instance with allocated slots. + pub const fn new() -> Self { + Self { cumulative_gas_used: 0, cumulative_da_bytes_used: 0, total_fees: U256::ZERO } + } + + /// Returns true if the transaction would exceed the block limits: + /// - block gas limit: ensures the transaction still fits into the block. + pub fn is_tx_over_limits( + &self, + tx: &(impl Encodable + Transaction), + block_gas_limit: u64, + block_data_limit: Option<u64>, + ) -> bool { + if block_data_limit + .is_some_and(|da_limit| self.cumulative_da_bytes_used + tx.length() as u64 > da_limit) + { + return true; + } + + self.cumulative_gas_used + tx.gas_limit() > block_gas_limit + } +}
diff --git reth/crates/scroll/payload/src/config.rs scroll-reth/crates/scroll/payload/src/config.rs new file mode 100644 index 0000000000000000000000000000000000000000..bbccb8683843dff7c77a90d700751b85b989fdb4 --- /dev/null +++ scroll-reth/crates/scroll/payload/src/config.rs @@ -0,0 +1,131 @@ +//! Configuration for the payload builder. + +use core::time::Duration; +use reth_chainspec::MIN_TRANSACTION_GAS; +use std::{fmt::Debug, time::Instant}; + +/// Settings for the Scroll builder. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct ScrollBuilderConfig { + /// Gas limit. + pub gas_limit: Option<u64>, + /// Time limit for payload building. + pub time_limit: Duration, + /// Maximum total data availability size for a block. + pub max_da_block_size: Option<u64>, +} + +/// Minimal data bytes size per transaction. +pub const MIN_TRANSACTION_DATA_SIZE: u64 = 115u64; + +impl ScrollBuilderConfig { + /// Returns a new instance of [`ScrollBuilderConfig`]. + pub const fn new( + gas_limit: Option<u64>, + time_limit: Duration, + max_da_block_size: Option<u64>, + ) -> Self { + Self { gas_limit, time_limit, max_da_block_size } + } + + /// Returns the [`PayloadBuildingBreaker`] for the config. + pub(super) fn breaker(&self) -> PayloadBuildingBreaker { + PayloadBuildingBreaker::new(self.time_limit, self.gas_limit, self.max_da_block_size) + } +} + +/// Used in the [`super::ScrollPayloadBuilder`] to exit the transactions execution loop early. +#[derive(Debug, Clone)] +pub struct PayloadBuildingBreaker { + start: Instant, + time_limit: Duration, + gas_limit: Option<u64>, + max_da_block_size: Option<u64>, +} + +impl PayloadBuildingBreaker { + /// Returns a new instance of the [`PayloadBuildingBreaker`]. + fn new(time_limit: Duration, gas_limit: Option<u64>, max_da_block_size: Option<u64>) -> Self { + Self { start: Instant::now(), time_limit, gas_limit, max_da_block_size } + } + + /// Returns whether the payload building should stop. + pub(super) fn should_break( + &self, + cumulative_gas_used: u64, + cumulative_da_size_used: u64, + ) -> bool { + // Check time limit + if self.start.elapsed() >= self.time_limit { + return true; + } + + // Check gas limit if configured + if let Some(gas_limit) = self.gas_limit { + if cumulative_gas_used > gas_limit.saturating_sub(MIN_TRANSACTION_GAS) { + return true; + } + } + + // Check data availability size limit if configured + if let Some(max_size) = self.max_da_block_size { + if cumulative_da_size_used > max_size.saturating_sub(MIN_TRANSACTION_DATA_SIZE) { + return true; + } + } + + false + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_should_break_on_time_limit() { + let breaker = PayloadBuildingBreaker::new( + Duration::from_millis(200), + Some(2 * MIN_TRANSACTION_GAS), + Some(2 * MIN_TRANSACTION_DATA_SIZE), + ); + assert!(!breaker.should_break(MIN_TRANSACTION_GAS, MIN_TRANSACTION_DATA_SIZE)); + std::thread::sleep(Duration::from_millis(201)); + assert!(breaker.should_break(MIN_TRANSACTION_GAS, MIN_TRANSACTION_DATA_SIZE)); + } + + #[test] + fn test_should_break_on_gas_limit() { + let breaker = PayloadBuildingBreaker::new( + Duration::from_secs(1), + Some(2 * MIN_TRANSACTION_GAS), + Some(2 * MIN_TRANSACTION_DATA_SIZE), + ); + assert!(!breaker.should_break(MIN_TRANSACTION_GAS, MIN_TRANSACTION_DATA_SIZE)); + assert!(breaker.should_break(MIN_TRANSACTION_GAS + 1, MIN_TRANSACTION_DATA_SIZE)); + } + + #[test] + fn test_should_break_on_data_size_limit() { + let breaker = PayloadBuildingBreaker::new( + Duration::from_secs(1), + Some(2 * MIN_TRANSACTION_GAS), + Some(2 * MIN_TRANSACTION_DATA_SIZE), + ); + assert!(!breaker.should_break(MIN_TRANSACTION_GAS, MIN_TRANSACTION_DATA_SIZE)); + assert!(breaker.should_break(MIN_TRANSACTION_GAS, MIN_TRANSACTION_DATA_SIZE + 1)); + } + + #[test] + fn test_should_break_with_no_da_limit() { + let breaker = PayloadBuildingBreaker::new( + Duration::from_secs(1), + Some(2 * MIN_TRANSACTION_GAS), + None, // No DA limit + ); + // Should not break on large DA size when no limit is set + assert!(!breaker.should_break(MIN_TRANSACTION_GAS, u64::MAX)); + // But should still break on gas limit + assert!(breaker.should_break(MIN_TRANSACTION_GAS + 1, u64::MAX)); + } +}
diff --git reth/crates/scroll/payload/src/error.rs scroll-reth/crates/scroll/payload/src/error.rs new file mode 100644 index 0000000000000000000000000000000000000000..0dbbd321af008a79b596aab44dc9a08cf23c961f --- /dev/null +++ scroll-reth/crates/scroll/payload/src/error.rs @@ -0,0 +1,11 @@ +/// Scroll specific payload building errors. +#[derive(Debug, thiserror::Error)] +pub enum ScrollPayloadBuilderError { + /// Thrown when a transaction fails to convert to a + /// [`alloy_consensus::transaction::Recovered`]. + #[error("failed to convert deposit transaction to RecoveredTx")] + TransactionEcRecoverFailed, + /// Thrown when a blob transaction is included in a sequencer's block. + #[error("blob transaction included in sequencer block")] + BlobTransactionRejected, +}
diff --git reth/crates/scroll/payload/src/lib.rs scroll-reth/crates/scroll/payload/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..5e3ab637e8b14425d7a4d479bfba7b548bdd1d29 --- /dev/null +++ scroll-reth/crates/scroll/payload/src/lib.rs @@ -0,0 +1,16 @@ +//! Engine Payload related types. + +pub mod builder; +pub use builder::{ScrollPayloadBuilder, ScrollPayloadTransactions}; + +pub mod config; +pub use config::ScrollBuilderConfig; + +mod error; +pub use error::ScrollPayloadBuilderError; + +#[cfg(feature = "test-utils")] +mod test_utils; + +#[cfg(feature = "test-utils")] +pub use test_utils::{NoopPayloadJob, NoopPayloadJobGenerator};
diff --git reth/crates/scroll/payload/src/test_utils.rs scroll-reth/crates/scroll/payload/src/test_utils.rs new file mode 100644 index 0000000000000000000000000000000000000000..a9d494f40fdaeef1faa719cff26319224f974cf9 --- /dev/null +++ scroll-reth/crates/scroll/payload/src/test_utils.rs @@ -0,0 +1,70 @@ +use core::{ + fmt::Debug, + future::Future, + pin::Pin, + task::{Context, Poll}, +}; +use reth_payload_builder::{KeepPayloadJobAlive, PayloadJob, PayloadJobGenerator}; +use reth_payload_primitives::{ + BuiltPayload, PayloadBuilderAttributes, PayloadBuilderError, PayloadKind, +}; + +/// A [`PayloadJobGenerator`] that doesn't produce any useful payload. +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct NoopPayloadJobGenerator<PA, BP> { + _types: core::marker::PhantomData<(PA, BP)>, +} + +impl<PA, BP> PayloadJobGenerator for NoopPayloadJobGenerator<PA, BP> +where + PA: PayloadBuilderAttributes + Default + Debug + Send + Sync, + BP: BuiltPayload + Default + Clone + Debug + Send + Sync + 'static, +{ + type Job = NoopPayloadJob<PA, BP>; + + fn new_payload_job(&self, _attr: PA) -> Result<Self::Job, PayloadBuilderError> { + Ok(NoopPayloadJob::<PA, BP>::default()) + } +} + +/// A [`PayloadJobGenerator`] that doesn't produce any payload. +#[derive(Debug, Default)] +pub struct NoopPayloadJob<PA, BP> { + _types: core::marker::PhantomData<(PA, BP)>, +} + +impl<PA, BP> Future for NoopPayloadJob<PA, BP> { + type Output = Result<(), PayloadBuilderError>; + + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> { + Poll::Pending + } +} + +impl<PA, BP> PayloadJob for NoopPayloadJob<PA, BP> +where + PA: PayloadBuilderAttributes + Default + Debug, + BP: BuiltPayload + Default + Clone + Debug + 'static, +{ + type PayloadAttributes = PA; + type ResolvePayloadFuture = + futures_util::future::Ready<Result<Self::BuiltPayload, PayloadBuilderError>>; + type BuiltPayload = BP; + + fn best_payload(&self) -> Result<Self::BuiltPayload, PayloadBuilderError> { + Ok(Self::BuiltPayload::default()) + } + + fn payload_attributes(&self) -> Result<Self::PayloadAttributes, PayloadBuilderError> { + Ok(Self::PayloadAttributes::default()) + } + + fn resolve_kind( + &mut self, + _kind: PayloadKind, + ) -> (Self::ResolvePayloadFuture, KeepPayloadJobAlive) { + let fut = futures_util::future::ready(self.best_payload()); + (fut, KeepPayloadJobAlive::No) + } +}
diff --git reth/crates/scroll/primitives/Cargo.toml scroll-reth/crates/scroll/primitives/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..be21143bb6ab19bf055b7c8a2934681f4a8ff74c --- /dev/null +++ scroll-reth/crates/scroll/primitives/Cargo.toml @@ -0,0 +1,94 @@ +[package] +name = "reth-scroll-primitives" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +# reth +reth-codecs = { workspace = true, optional = true } +reth-primitives-traits = { workspace = true, features = ["scroll-alloy-traits"] } +reth-zstd-compressors = { workspace = true, optional = true } + +# alloy +alloy-consensus.workspace = true +alloy-eips.workspace = true +alloy-primitives.workspace = true +alloy-rlp.workspace = true + +# scroll +scroll-alloy-consensus.workspace = true + +# codec +bytes = { workspace = true, optional = true } +modular-bitfield = { workspace = true, optional = true } +serde = { workspace = true, optional = true } + +# misc +once_cell.workspace = true + +# test +arbitrary = { workspace = true, features = ["derive"], optional = true } + +[dev-dependencies] +reth-codecs = { workspace = true, features = ["test-utils"] } +rstest.workspace = true +rand.workspace = true + +[features] +default = ["std"] +std = [ + "serde?/std", + "scroll-alloy-consensus/std", + "alloy-consensus/std", + "alloy-eips/std", + "alloy-primitives/std", + "alloy-rlp/std", + "bytes?/std", + "reth-primitives-traits/std", + "reth-zstd-compressors?/std", + "reth-codecs?/std", + "once_cell/std", + "serde?/std", +] +reth-codec = [ + "dep:reth-codecs", + "std", + "reth-primitives-traits/reth-codec", + "scroll-alloy-consensus/reth-codec", + "dep:bytes", + "dep:modular-bitfield", + "dep:reth-zstd-compressors", +] +serde = [ + "dep:serde", + "scroll-alloy-consensus/serde", + "alloy-consensus/serde", + "alloy-eips/serde", + "alloy-primitives/serde", + "bytes?/serde", + "reth-codecs?/serde", + "reth-primitives-traits/serde", + "rand/serde", +] +serde-bincode-compat = [ + "alloy-consensus/serde-bincode-compat", + "alloy-eips/serde-bincode-compat", + "reth-primitives-traits/serde-bincode-compat", + "scroll-alloy-consensus/serde-bincode-compat", +] +arbitrary = [ + "dep:arbitrary", + "alloy-consensus/arbitrary", + "alloy-eips/arbitrary", + "alloy-primitives/arbitrary", + "reth-codecs?/arbitrary", + "reth-primitives-traits/arbitrary", + "scroll-alloy-consensus/arbitrary", +]
diff --git reth/crates/scroll/primitives/src/lib.rs scroll-reth/crates/scroll/primitives/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..232cfc9a79fc56221b92db63e9cee28369ac19ce --- /dev/null +++ scroll-reth/crates/scroll/primitives/src/lib.rs @@ -0,0 +1,39 @@ +//! Commonly used types in Scroll. + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/scroll-tech/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] + +use once_cell as _; + +pub mod transaction; +pub use transaction::{tx_type::ScrollTxType, ScrollTransactionSigned}; + +use reth_primitives_traits::Block; + +mod receipt; +pub use receipt::ScrollReceipt; + +/// Scroll-specific block type. +pub type ScrollBlock = alloy_consensus::Block<ScrollTransactionSigned>; + +/// Scroll-specific block body type. +pub type ScrollBlockBody = <ScrollBlock as Block>::Body; + +/// Primitive types for Scroll Node. +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct ScrollPrimitives; + +#[cfg(feature = "serde-bincode-compat")] +impl reth_primitives_traits::NodePrimitives for ScrollPrimitives { + type Block = ScrollBlock; + type BlockHeader = alloy_consensus::Header; + type BlockBody = ScrollBlockBody; + type SignedTx = ScrollTransactionSigned; + type Receipt = ScrollReceipt; +}
diff --git reth/crates/scroll/primitives/src/receipt.rs scroll-reth/crates/scroll/primitives/src/receipt.rs new file mode 100644 index 0000000000000000000000000000000000000000..6e214b310897601a5249cd13b2bd67aa45acc71d --- /dev/null +++ scroll-reth/crates/scroll/primitives/src/receipt.rs @@ -0,0 +1,441 @@ +use alloy_consensus::{ + proofs::ordered_trie_root_with_encoder, Eip2718EncodableReceipt, Eip658Value, Receipt, + ReceiptWithBloom, RlpDecodableReceipt, RlpEncodableReceipt, TxReceipt, Typed2718, +}; +use alloy_eips::{ + eip2718::{Eip2718Result, Encodable2718}, + Decodable2718, +}; +use alloy_primitives::{Bloom, Log, B256, U256}; +use alloy_rlp::{BufMut, Decodable, Encodable, Header}; +use reth_primitives_traits::InMemorySize; +use scroll_alloy_consensus::{ScrollTransactionReceipt, ScrollTxType}; + +/// Typed ethereum transaction receipt. +/// Receipt containing result of transaction execution. +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub enum ScrollReceipt { + /// Legacy receipt + Legacy(ScrollTransactionReceipt), + /// EIP-2930 receipt + Eip2930(ScrollTransactionReceipt), + /// EIP-1559 receipt + Eip1559(ScrollTransactionReceipt), + /// EIP-7702 receipt + Eip7702(ScrollTransactionReceipt), + /// L1 message receipt + L1Message(Receipt), +} + +impl ScrollReceipt { + /// Returns [`ScrollTxType`] of the receipt. + pub const fn tx_type(&self) -> ScrollTxType { + match self { + Self::Legacy(_) => ScrollTxType::Legacy, + Self::Eip2930(_) => ScrollTxType::Eip2930, + Self::Eip1559(_) => ScrollTxType::Eip1559, + Self::Eip7702(_) => ScrollTxType::Eip7702, + Self::L1Message(_) => ScrollTxType::L1Message, + } + } + + /// Returns inner [`Receipt`], + pub const fn as_receipt(&self) -> &Receipt { + match self { + Self::Legacy(receipt) | + Self::Eip2930(receipt) | + Self::Eip1559(receipt) | + Self::Eip7702(receipt) => &receipt.inner, + Self::L1Message(receipt) => receipt, + } + } + + /// Returns length of RLP-encoded receipt fields with the given [`Bloom`] without an RLP header. + pub fn rlp_encoded_fields_length(&self, bloom: &Bloom) -> usize { + match self { + Self::Legacy(receipt) | + Self::Eip2930(receipt) | + Self::Eip1559(receipt) | + Self::Eip7702(receipt) => receipt.rlp_encoded_fields_length_with_bloom(bloom), + Self::L1Message(receipt) => receipt.rlp_encoded_fields_length_with_bloom(bloom), + } + } + + /// RLP-encodes receipt fields with the given [`Bloom`] without an RLP header. + pub fn rlp_encode_fields(&self, bloom: &Bloom, out: &mut dyn BufMut) { + match self { + Self::Legacy(receipt) | + Self::Eip2930(receipt) | + Self::Eip1559(receipt) | + Self::Eip7702(receipt) => receipt.rlp_encode_fields_with_bloom(bloom, out), + Self::L1Message(receipt) => receipt.rlp_encode_fields_with_bloom(bloom, out), + } + } + + /// Returns RLP header for inner encoding. + pub fn rlp_header_inner(&self, bloom: &Bloom) -> Header { + Header { list: true, payload_length: self.rlp_encoded_fields_length(bloom) } + } + + /// Returns RLP header for inner encoding without bloom. + pub fn rlp_header_inner_without_bloom(&self) -> Header { + Header { list: true, payload_length: self.rlp_encoded_fields_length_without_bloom() } + } + + /// RLP-decodes the receipt from the provided buffer. This does not expect a type byte or + /// network header. + pub fn rlp_decode_inner( + buf: &mut &[u8], + tx_type: ScrollTxType, + ) -> alloy_rlp::Result<ReceiptWithBloom<Self>> { + match tx_type { + ScrollTxType::Legacy => { + let ReceiptWithBloom { receipt, logs_bloom } = + RlpDecodableReceipt::rlp_decode_with_bloom(buf)?; + Ok(ReceiptWithBloom { receipt: Self::Legacy(receipt), logs_bloom }) + } + ScrollTxType::Eip2930 => { + let ReceiptWithBloom { receipt, logs_bloom } = + RlpDecodableReceipt::rlp_decode_with_bloom(buf)?; + Ok(ReceiptWithBloom { receipt: Self::Eip2930(receipt), logs_bloom }) + } + ScrollTxType::Eip1559 => { + let ReceiptWithBloom { receipt, logs_bloom } = + RlpDecodableReceipt::rlp_decode_with_bloom(buf)?; + Ok(ReceiptWithBloom { receipt: Self::Eip1559(receipt), logs_bloom }) + } + ScrollTxType::Eip7702 => { + let ReceiptWithBloom { receipt, logs_bloom } = + RlpDecodableReceipt::rlp_decode_with_bloom(buf)?; + Ok(ReceiptWithBloom { receipt: Self::Eip7702(receipt), logs_bloom }) + } + ScrollTxType::L1Message => { + let ReceiptWithBloom { receipt, logs_bloom } = + RlpDecodableReceipt::rlp_decode_with_bloom(buf)?; + Ok(ReceiptWithBloom { receipt: Self::L1Message(receipt), logs_bloom }) + } + } + } + + /// RLP-encodes receipt fields without an RLP header. + pub fn rlp_encode_fields_without_bloom(&self, out: &mut dyn BufMut) { + match self { + Self::Legacy(receipt) | + Self::Eip2930(receipt) | + Self::Eip1559(receipt) | + Self::Eip7702(receipt) => { + receipt.inner.status.encode(out); + receipt.inner.cumulative_gas_used.encode(out); + receipt.inner.logs.encode(out); + } + Self::L1Message(receipt) => { + receipt.status.encode(out); + receipt.cumulative_gas_used.encode(out); + receipt.logs.encode(out); + } + } + } + + /// Returns length of RLP-encoded receipt fields without an RLP header. + pub fn rlp_encoded_fields_length_without_bloom(&self) -> usize { + match self { + Self::Legacy(receipt) | + Self::Eip2930(receipt) | + Self::Eip1559(receipt) | + Self::Eip7702(receipt) => { + receipt.inner.status.length() + + receipt.inner.cumulative_gas_used.length() + + receipt.inner.logs.length() + } + Self::L1Message(receipt) => { + receipt.status.length() + + receipt.cumulative_gas_used.length() + + receipt.logs.length() + } + } + } + + /// RLP-decodes the receipt from the provided buffer without bloom. + pub fn rlp_decode_inner_without_bloom( + buf: &mut &[u8], + tx_type: ScrollTxType, + ) -> alloy_rlp::Result<Self> { + let header = Header::decode(buf)?; + if !header.list { + return Err(alloy_rlp::Error::UnexpectedString); + } + + let remaining = buf.len(); + let status = Decodable::decode(buf)?; + let cumulative_gas_used = Decodable::decode(buf)?; + let logs = Decodable::decode(buf)?; + + if buf.len() + header.payload_length != remaining { + return Err(alloy_rlp::Error::UnexpectedLength); + } + + let inner = Receipt { status, cumulative_gas_used, logs }; + + match tx_type { + ScrollTxType::Legacy => { + Ok(Self::Legacy(ScrollTransactionReceipt { inner, l1_fee: Default::default() })) + } + ScrollTxType::Eip2930 => { + Ok(Self::Eip2930(ScrollTransactionReceipt { inner, l1_fee: Default::default() })) + } + ScrollTxType::Eip1559 => { + Ok(Self::Eip1559(ScrollTransactionReceipt { inner, l1_fee: Default::default() })) + } + ScrollTxType::Eip7702 => { + Ok(Self::Eip7702(ScrollTransactionReceipt { inner, l1_fee: Default::default() })) + } + ScrollTxType::L1Message => Ok(Self::L1Message(inner)), + } + } + + /// Returns the l1 fee for the transaction receipt. + pub const fn l1_fee(&self) -> U256 { + match self { + Self::Legacy(receipt) | + Self::Eip2930(receipt) | + Self::Eip1559(receipt) | + Self::Eip7702(receipt) => receipt.l1_fee, + Self::L1Message(_) => U256::ZERO, + } + } + + /// Calculates the receipt root for a header for the reference type of [Receipt]. + /// + /// NOTE: Prefer `proofs::calculate_receipt_root` if you have log blooms memoized. + pub fn calculate_receipt_root_no_memo(receipts: &[Self]) -> B256 { + ordered_trie_root_with_encoder(receipts, |r, buf| r.with_bloom_ref().encode_2718(buf)) + } +} + +impl Eip2718EncodableReceipt for ScrollReceipt { + fn eip2718_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize { + !self.tx_type().is_legacy() as usize + self.rlp_header_inner(bloom).length_with_payload() + } + + fn eip2718_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) { + if !self.tx_type().is_legacy() { + out.put_u8(self.tx_type() as u8); + } + self.rlp_header_inner(bloom).encode(out); + self.rlp_encode_fields(bloom, out); + } +} + +impl RlpEncodableReceipt for ScrollReceipt { + fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize { + let mut len = self.eip2718_encoded_length_with_bloom(bloom); + if !self.tx_type().is_legacy() { + len += Header { + list: false, + payload_length: self.eip2718_encoded_length_with_bloom(bloom), + } + .length(); + } + + len + } + + fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) { + if !self.tx_type().is_legacy() { + Header { list: false, payload_length: self.eip2718_encoded_length_with_bloom(bloom) } + .encode(out); + } + self.eip2718_encode_with_bloom(bloom, out); + } +} + +impl RlpDecodableReceipt for ScrollReceipt { + fn rlp_decode_with_bloom(buf: &mut &[u8]) -> alloy_rlp::Result<ReceiptWithBloom<Self>> { + let header_buf = &mut &**buf; + let header = Header::decode(header_buf)?; + + // Legacy receipt, reuse initial buffer without advancing + if header.list { + return Self::rlp_decode_inner(buf, ScrollTxType::Legacy) + } + + // Otherwise, advance the buffer and try decoding type flag followed by receipt + *buf = *header_buf; + + let remaining = buf.len(); + let tx_type = ScrollTxType::decode(buf)?; + let this = Self::rlp_decode_inner(buf, tx_type)?; + + if buf.len() + header.payload_length != remaining { + return Err(alloy_rlp::Error::UnexpectedLength); + } + + Ok(this) + } +} + +impl Encodable2718 for ScrollReceipt { + fn encode_2718_len(&self) -> usize { + !self.tx_type().is_legacy() as usize + + self.rlp_header_inner_without_bloom().length_with_payload() + } + + fn encode_2718(&self, out: &mut dyn BufMut) { + if !self.tx_type().is_legacy() { + out.put_u8(self.tx_type() as u8); + } + self.rlp_header_inner_without_bloom().encode(out); + self.rlp_encode_fields_without_bloom(out); + } +} + +impl Decodable2718 for ScrollReceipt { + fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> { + Ok(Self::rlp_decode_inner_without_bloom(buf, ScrollTxType::try_from(ty)?)?) + } + + fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> { + Ok(Self::rlp_decode_inner_without_bloom(buf, ScrollTxType::Legacy)?) + } +} + +impl Encodable for ScrollReceipt { + fn encode(&self, out: &mut dyn BufMut) { + self.network_encode(out); + } + + fn length(&self) -> usize { + self.network_len() + } +} + +impl Decodable for ScrollReceipt { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> { + Ok(Self::network_decode(buf)?) + } +} + +impl TxReceipt for ScrollReceipt { + type Log = Log; + + fn status_or_post_state(&self) -> Eip658Value { + self.as_receipt().status_or_post_state() + } + + fn status(&self) -> bool { + self.as_receipt().status() + } + + fn bloom(&self) -> Bloom { + self.as_receipt().bloom() + } + + fn cumulative_gas_used(&self) -> u64 { + self.as_receipt().cumulative_gas_used() + } + + fn logs(&self) -> &[Log] { + self.as_receipt().logs() + } +} + +impl Typed2718 for ScrollReceipt { + fn ty(&self) -> u8 { + self.tx_type().into() + } +} + +impl InMemorySize for ScrollReceipt { + fn size(&self) -> usize { + self.as_receipt().size() + } +} + +#[cfg(feature = "serde-bincode-compat")] +impl reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat for ScrollReceipt { + type BincodeRepr<'a> = Self; + + fn as_repr(&self) -> Self::BincodeRepr<'_> { + self.clone() + } + + fn from_repr(repr: Self::BincodeRepr<'_>) -> Self { + repr + } +} + +#[cfg(feature = "reth-codec")] +mod compact { + use super::*; + use alloy_primitives::U256; + use reth_codecs::Compact; + use std::borrow::Cow; + + #[derive(reth_codecs::CompactZstd)] + #[reth_zstd( + compressor = reth_zstd_compressors::RECEIPT_COMPRESSOR, + decompressor = reth_zstd_compressors::RECEIPT_DECOMPRESSOR + )] + struct CompactScrollReceipt<'a> { + tx_type: ScrollTxType, + success: bool, + cumulative_gas_used: u64, + #[allow(clippy::owned_cow)] + logs: Cow<'a, Vec<Log>>, + l1_fee: Option<U256>, + } + + impl<'a> From<&'a ScrollReceipt> for CompactScrollReceipt<'a> { + fn from(receipt: &'a ScrollReceipt) -> Self { + Self { + tx_type: receipt.tx_type(), + success: receipt.status(), + cumulative_gas_used: receipt.cumulative_gas_used(), + logs: Cow::Borrowed(&receipt.as_receipt().logs), + l1_fee: (receipt.l1_fee() != U256::ZERO).then_some(receipt.l1_fee()), + } + } + } + + impl From<CompactScrollReceipt<'_>> for ScrollReceipt { + fn from(receipt: CompactScrollReceipt<'_>) -> Self { + let CompactScrollReceipt { tx_type, success, cumulative_gas_used, logs, l1_fee } = + receipt; + + let inner = + Receipt { status: success.into(), cumulative_gas_used, logs: logs.into_owned() }; + + match tx_type { + ScrollTxType::Legacy => { + Self::Legacy(ScrollTransactionReceipt::new(inner, l1_fee.unwrap_or_default())) + } + ScrollTxType::Eip2930 => { + Self::Eip2930(ScrollTransactionReceipt::new(inner, l1_fee.unwrap_or_default())) + } + ScrollTxType::Eip1559 => { + Self::Eip1559(ScrollTransactionReceipt::new(inner, l1_fee.unwrap_or_default())) + } + ScrollTxType::Eip7702 => { + Self::Eip7702(ScrollTransactionReceipt::new(inner, l1_fee.unwrap_or_default())) + } + ScrollTxType::L1Message => Self::L1Message(inner), + } + } + } + + impl Compact for ScrollReceipt { + fn to_compact<B>(&self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + CompactScrollReceipt::from(self).to_compact(buf) + } + + fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) { + let (receipt, buf) = CompactScrollReceipt::from_compact(buf, len); + (receipt.into(), buf) + } + } +}
diff --git reth/crates/scroll/primitives/src/transaction/mod.rs scroll-reth/crates/scroll/primitives/src/transaction/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..87471e507e9013fa44fb153d98f5553d1bc74e19 --- /dev/null +++ scroll-reth/crates/scroll/primitives/src/transaction/mod.rs @@ -0,0 +1,6 @@ +//! Scroll primitives transaction types. + +pub mod tx_type; + +/// Signed transaction. +pub type ScrollTransactionSigned = scroll_alloy_consensus::ScrollTxEnvelope;
diff --git reth/crates/scroll/primitives/src/transaction/tx_type.rs scroll-reth/crates/scroll/primitives/src/transaction/tx_type.rs new file mode 100644 index 0000000000000000000000000000000000000000..e549b81bd97a09f15f1b00dc99f1f2a34cb292b0 --- /dev/null +++ scroll-reth/crates/scroll/primitives/src/transaction/tx_type.rs @@ -0,0 +1,47 @@ +//! Scroll transaction type. + +pub use scroll_alloy_consensus::ScrollTxType; + +#[cfg(test)] +mod tests { + use super::*; + use reth_codecs::{txtype::*, Compact}; + use rstest::rstest; + use scroll_alloy_consensus::L1_MESSAGE_TX_TYPE_ID; + + #[rstest] + #[case(ScrollTxType::Legacy, COMPACT_IDENTIFIER_LEGACY, vec![])] + #[case(ScrollTxType::Eip2930, COMPACT_IDENTIFIER_EIP2930, vec![])] + #[case(ScrollTxType::Eip1559, COMPACT_IDENTIFIER_EIP1559, vec![])] + #[case(ScrollTxType::L1Message, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![L1_MESSAGE_TX_TYPE_ID])] + fn test_txtype_to_compact( + #[case] tx_type: ScrollTxType, + #[case] expected_identifier: usize, + #[case] expected_buf: Vec<u8>, + ) { + let mut buf = vec![]; + let identifier = tx_type.to_compact(&mut buf); + + assert_eq!( + identifier, expected_identifier, + "Unexpected identifier for ScrollTxType {tx_type:?}", + ); + assert_eq!(buf, expected_buf, "Unexpected buffer for ScrollTxType {tx_type:?}",); + } + + #[rstest] + #[case(ScrollTxType::Legacy, COMPACT_IDENTIFIER_LEGACY, vec![])] + #[case(ScrollTxType::Eip2930, COMPACT_IDENTIFIER_EIP2930, vec![])] + #[case(ScrollTxType::Eip1559, COMPACT_IDENTIFIER_EIP1559, vec![])] + #[case(ScrollTxType::L1Message, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![L1_MESSAGE_TX_TYPE_ID])] + fn test_txtype_from_compact( + #[case] expected_type: ScrollTxType, + #[case] identifier: usize, + #[case] buf: Vec<u8>, + ) { + let (actual_type, remaining_buf) = ScrollTxType::from_compact(&buf, identifier); + + assert_eq!(actual_type, expected_type, "Unexpected TxType for identifier {identifier}"); + assert!(remaining_buf.is_empty(), "Buffer not fully consumed for identifier {identifier}"); + } +}
diff --git reth/crates/scroll/rpc/Cargo.toml scroll-reth/crates/scroll/rpc/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..91850423b940d3ccdc36d880463fbf7839fc52eb --- /dev/null +++ scroll-reth/crates/scroll/rpc/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "reth-scroll-rpc" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "Ethereum RPC implementation for scroll." + +[lints] +workspace = true + +[dependencies] +# reth +reth-chainspec.workspace = true +reth-evm.workspace = true +reth-primitives-traits.workspace = true +reth-provider.workspace = true +reth-rpc-eth-api = { workspace = true, features = ["scroll"] } +reth-rpc-eth-types.workspace = true +reth-tasks = { workspace = true, features = ["rayon"] } +reth-transaction-pool.workspace = true +reth-rpc = { workspace = true, features = ["scroll"] } +reth-rpc-convert = { workspace = true, features = ["scroll"] } +reth-node-api.workspace = true +reth-node-builder.workspace = true + +# scroll +reth-scroll-chainspec.workspace = true +reth-scroll-evm.workspace = true +reth-scroll-primitives = { workspace = true, features = ["serde", "serde-bincode-compat", "reth-codec"] } +scroll-alloy-consensus.workspace = true +scroll-alloy-hardforks.workspace = true +scroll-alloy-network.workspace = true +scroll-alloy-rpc-types.workspace = true + +# ethereum +alloy-primitives.workspace = true +alloy-rpc-types-eth.workspace = true +alloy-consensus.workspace = true +alloy-eips.workspace = true +revm.workspace = true +alloy-transport.workspace = true +alloy-json-rpc.workspace = true +alloy-rpc-client.workspace = true +alloy-transport-http.workspace = true + +# reqwest +reqwest = { workspace = true, default-features = false, features = ["rustls-tls-native-roots"] } + +# tracing +tracing.workspace = true + +# async +tokio.workspace = true + +# rpc +jsonrpsee-types.workspace = true + +# misc +eyre.workspace = true +thiserror.workspace = true
diff --git reth/crates/scroll/rpc/src/error.rs scroll-reth/crates/scroll/rpc/src/error.rs new file mode 100644 index 0000000000000000000000000000000000000000..5471ccf1c8c8ddccdf2a68ab5df567306db25a3c --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/error.rs @@ -0,0 +1,107 @@ +//! RPC errors specific to Scroll. + +use alloy_json_rpc::ErrorPayload; +use alloy_rpc_types_eth::BlockError; +use alloy_transport::{RpcError, TransportErrorKind}; +use jsonrpsee_types::error::INTERNAL_ERROR_CODE; +use reth_evm::execute::ProviderError; +use reth_rpc_convert::transaction::EthTxEnvError; +use reth_rpc_eth_api::{AsEthApiError, TransactionConversionError}; +use reth_rpc_eth_types::{error::api::FromEvmHalt, EthApiError}; +use revm::context::result::{EVMError, HaltReason}; + +/// Scroll specific errors, that extend [`EthApiError`]. +#[derive(Debug, thiserror::Error)] +pub enum ScrollEthApiError { + /// L1 ethereum error. + #[error(transparent)] + Eth(#[from] EthApiError), + /// Sequencer client error. + #[error(transparent)] + Sequencer(#[from] SequencerClientError), +} + +impl AsEthApiError for ScrollEthApiError { + fn as_err(&self) -> Option<&EthApiError> { + match self { + Self::Eth(err) => Some(err), + _ => None, + } + } +} + +impl From<ScrollEthApiError> for jsonrpsee_types::error::ErrorObject<'static> { + fn from(err: ScrollEthApiError) -> Self { + match err { + ScrollEthApiError::Eth(err) => err.into(), + ScrollEthApiError::Sequencer(err) => err.into(), + } + } +} + +impl From<EthTxEnvError> for ScrollEthApiError { + fn from(value: EthTxEnvError) -> Self { + Self::Eth(EthApiError::from(value)) + } +} + +impl From<BlockError> for ScrollEthApiError { + fn from(error: BlockError) -> Self { + Self::Eth(error.into()) + } +} + +impl<DB> From<EVMError<DB>> for ScrollEthApiError +where + EthApiError: From<EVMError<DB>>, +{ + fn from(error: EVMError<DB>) -> Self { + Self::Eth(error.into()) + } +} + +impl FromEvmHalt<HaltReason> for ScrollEthApiError { + fn from_evm_halt(halt: HaltReason, gas_limit: u64) -> Self { + EthApiError::from_evm_halt(halt, gas_limit).into() + } +} + +impl From<TransactionConversionError> for ScrollEthApiError { + fn from(value: TransactionConversionError) -> Self { + Self::Eth(EthApiError::from(value)) + } +} + +impl From<ProviderError> for ScrollEthApiError { + fn from(value: ProviderError) -> Self { + Self::Eth(EthApiError::from(value)) + } +} + +/// Error type when interacting with the Sequencer +#[derive(Debug, thiserror::Error)] +pub enum SequencerClientError { + /// Wrapper around an [`RpcError<TransportErrorKind>`]. + #[error(transparent)] + HttpError(#[from] RpcError<TransportErrorKind>), + /// Thrown when serializing transaction to forward to sequencer + #[error("invalid sequencer transaction")] + InvalidSequencerTransaction, +} + +impl From<SequencerClientError> for jsonrpsee_types::error::ErrorObject<'static> { + fn from(err: SequencerClientError) -> Self { + match err { + SequencerClientError::HttpError(RpcError::ErrorResp(ErrorPayload { + code, + message, + data, + })) => jsonrpsee_types::error::ErrorObject::owned(code as i32, message, data), + err => jsonrpsee_types::error::ErrorObject::owned( + INTERNAL_ERROR_CODE, + err.to_string(), + None::<String>, + ), + } + } +}
diff --git reth/crates/scroll/rpc/src/eth/block.rs scroll-reth/crates/scroll/rpc/src/eth/block.rs new file mode 100644 index 0000000000000000000000000000000000000000..7dc8ec9951ad9c6d736ca8274e00c0e8fece11a2 --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/eth/block.rs @@ -0,0 +1,26 @@ +//! Loads and formats Scroll block RPC response. + +use crate::{ScrollEthApi, ScrollEthApiError}; + +use reth_rpc_convert::RpcConvert; +use reth_rpc_eth_api::{ + helpers::{EthBlocks, LoadBlock}, + RpcNodeCore, +}; +use reth_rpc_eth_types::error::FromEvmError; + +impl<N, Rpc> EthBlocks for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + ScrollEthApiError: FromEvmError<N::Evm>, + Rpc: RpcConvert<Primitives = N::Primitives, Error = ScrollEthApiError>, +{ +} + +impl<N, Rpc> LoadBlock for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + ScrollEthApiError: FromEvmError<N::Evm>, + Rpc: RpcConvert<Primitives = N::Primitives, Error = ScrollEthApiError>, +{ +}
diff --git reth/crates/scroll/rpc/src/eth/call.rs scroll-reth/crates/scroll/rpc/src/eth/call.rs new file mode 100644 index 0000000000000000000000000000000000000000..99249412db1d2d5258b5b1463810cab97635bf40 --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/eth/call.rs @@ -0,0 +1,44 @@ +use crate::{ScrollEthApi, ScrollEthApiError}; + +use reth_evm::TxEnvFor; +use reth_rpc_eth_api::{ + helpers::{estimate::EstimateCall, Call, EthCall}, + RpcConvert, RpcNodeCore, +}; +use reth_rpc_eth_types::error::FromEvmError; + +impl<N, Rpc> EthCall for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + ScrollEthApiError: FromEvmError<N::Evm>, + Rpc: + RpcConvert<Primitives = N::Primitives, Error = ScrollEthApiError, TxEnv = TxEnvFor<N::Evm>>, +{ +} + +impl<N, Rpc> EstimateCall for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + ScrollEthApiError: FromEvmError<N::Evm>, + Rpc: + RpcConvert<Primitives = N::Primitives, Error = ScrollEthApiError, TxEnv = TxEnvFor<N::Evm>>, +{ +} + +impl<N, Rpc> Call for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + ScrollEthApiError: FromEvmError<N::Evm>, + Rpc: + RpcConvert<Primitives = N::Primitives, Error = ScrollEthApiError, TxEnv = TxEnvFor<N::Evm>>, +{ + #[inline] + fn call_gas_limit(&self) -> u64 { + self.inner.eth_api.gas_cap() + } + + #[inline] + fn max_simulate_blocks(&self) -> u64 { + self.inner.eth_api.max_simulate_blocks() + } +}
diff --git reth/crates/scroll/rpc/src/eth/fee.rs scroll-reth/crates/scroll/rpc/src/eth/fee.rs new file mode 100644 index 0000000000000000000000000000000000000000..dd8803b114fa3734baeb22e7520d54d1b64b13a9 --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/eth/fee.rs @@ -0,0 +1,268 @@ +use crate::{ScrollEthApi, ScrollEthApiError}; + +use alloy_consensus::BlockHeader; +use alloy_eips::eip7840::BlobParams; +use alloy_primitives::{Sealable, U256}; +use alloy_rpc_types_eth::{BlockNumberOrTag, FeeHistory}; +use reth_chainspec::EthChainSpec; +use reth_primitives_traits::BlockBody; +use reth_provider::{ + BaseFeeProvider, BlockIdReader, ChainSpecProvider, HeaderProvider, ProviderHeader, + StateProviderFactory, +}; +use reth_rpc_convert::RpcConvert; +use reth_rpc_eth_api::{ + helpers::{EthFees, LoadFee}, + FromEthApiError, RpcNodeCore, RpcNodeCoreExt, +}; +use reth_rpc_eth_types::{ + error::FromEvmError, fee_history::calculate_reward_percentiles_for_block, EthApiError, +}; +use reth_scroll_chainspec::{ChainConfig, ScrollChainConfig}; +use reth_scroll_evm::ScrollBaseFeeProvider; +use scroll_alloy_hardforks::ScrollHardforks; +use std::future::Future; +use tracing::debug; + +impl<N, Rpc> EthFees for ScrollEthApi<N, Rpc> +where + Self: LoadFee< + Provider: StateProviderFactory + + ChainSpecProvider< + ChainSpec: EthChainSpec<Header = ProviderHeader<Self::Provider>> + + ScrollHardforks + + ChainConfig<Config = ScrollChainConfig>, + >, + >, + N: RpcNodeCore, + ScrollEthApiError: FromEvmError<N::Evm>, + Rpc: RpcConvert<Primitives = N::Primitives, Error = ScrollEthApiError>, +{ + #[allow(clippy::manual_async_fn)] + fn fee_history( + &self, + mut block_count: u64, + mut newest_block: BlockNumberOrTag, + reward_percentiles: Option<Vec<f64>>, + ) -> impl Future<Output = Result<FeeHistory, Self::Error>> + Send { + async move { + if block_count == 0 { + return Ok(FeeHistory::default()) + } + + // ensure the given reward percentiles aren't excessive + if reward_percentiles.as_ref().map(|perc| perc.len() as u64) > + Some(self.gas_oracle().config().max_reward_percentile_count) + { + return Err(EthApiError::InvalidRewardPercentiles.into()) + } + + // See https://github.com/ethereum/go-ethereum/blob/2754b197c935ee63101cbbca2752338246384fec/eth/gasprice/feehistory.go#L218C8-L225 + let max_fee_history = if reward_percentiles.is_none() { + self.gas_oracle().config().max_header_history + } else { + self.gas_oracle().config().max_block_history + }; + + if block_count > max_fee_history { + debug!( + requested = block_count, + truncated = max_fee_history, + "Sanitizing fee history block count" + ); + block_count = max_fee_history + } + + if newest_block.is_pending() { + // cap the target block since we don't have fee history for the pending block + newest_block = BlockNumberOrTag::Latest; + } + + let end_block = self + .provider() + .block_number_for_id(newest_block.into()) + .map_err(Self::Error::from_eth_err)? + .ok_or(EthApiError::HeaderNotFound(newest_block.into()))?; + + // need to add 1 to the end block to get the correct (inclusive) range + let end_block_plus = end_block + 1; + // Ensure that we would not be querying outside of genesis + if end_block_plus < block_count { + block_count = end_block_plus; + } + + // If reward percentiles were specified, we + // need to validate that they are monotonically + // increasing and 0 <= p <= 100 + // Note: The types used ensure that the percentiles are never < 0 + if let Some(percentiles) = &reward_percentiles { + if percentiles.windows(2).any(|w| w[0] > w[1] || w[0] > 100.) { + return Err(EthApiError::InvalidRewardPercentiles.into()) + } + } + + // Fetch the headers and ensure we got all of them + // + // Treat a request for 1 block as a request for `newest_block..=newest_block`, + // otherwise `newest_block - 2` + // NOTE: We ensured that block count is capped + let start_block = end_block_plus - block_count; + + // Collect base fees, gas usage ratios and (optionally) reward percentile data + let mut base_fee_per_gas: Vec<u128> = Vec::new(); + let mut gas_used_ratio: Vec<f64> = Vec::new(); + + let mut base_fee_per_blob_gas: Vec<u128> = Vec::new(); + let mut blob_gas_used_ratio: Vec<f64> = Vec::new(); + + let mut rewards: Vec<Vec<u128>> = Vec::new(); + + let chain_spec = self.provider().chain_spec(); + let base_fee_provider = ScrollBaseFeeProvider::new(chain_spec.clone()); + + // Check if the requested range is within the cache bounds + let fee_entries = self.fee_history_cache().get_history(start_block, end_block).await; + + if let Some(fee_entries) = fee_entries { + if fee_entries.len() != block_count as usize { + return Err(EthApiError::InvalidBlockRange.into()) + } + + for entry in &fee_entries { + base_fee_per_gas + .push(entry.header.base_fee_per_gas().unwrap_or_default() as u128); + gas_used_ratio.push(entry.gas_used_ratio); + base_fee_per_blob_gas.push(entry.base_fee_per_blob_gas.unwrap_or_default()); + blob_gas_used_ratio.push(entry.blob_gas_used_ratio); + + if let Some(percentiles) = &reward_percentiles { + let mut block_rewards = Vec::with_capacity(percentiles.len()); + for &percentile in percentiles { + block_rewards.push(self.approximate_percentile(entry, percentile)); + } + rewards.push(block_rewards); + } + } + let last_entry = fee_entries.last().expect("is not empty"); + + // Also need to include the `base_fee_per_gas` and `base_fee_per_blob_gas` for the + // next block + let mut provider = self + .provider() + .state_by_block_id(last_entry.header.hash_slow().into()) + .map_err(Into::<EthApiError>::into)?; + base_fee_per_gas.push( + base_fee_provider + .next_block_base_fee( + &mut provider, + &last_entry.header, + last_entry.header.timestamp(), + ) + .map_err(Into::<EthApiError>::into)? as u128, + ); + + base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default()); + } else { + // read the requested header range + let headers = self.provider() + .sealed_headers_range(start_block..=end_block) + .map_err(Self::Error::from_eth_err)?; + if headers.len() != block_count as usize { + return Err(EthApiError::InvalidBlockRange.into()) + } + + for header in &headers { + base_fee_per_gas.push(header.base_fee_per_gas().unwrap_or_default() as u128); + gas_used_ratio.push(header.gas_used() as f64 / header.gas_limit() as f64); + + let blob_params = chain_spec + .blob_params_at_timestamp(header.timestamp()) + .unwrap_or_else(BlobParams::cancun); + + base_fee_per_blob_gas.push(header.blob_fee(blob_params).unwrap_or_default()); + blob_gas_used_ratio.push( + header.blob_gas_used().unwrap_or_default() as f64 + / blob_params.max_blob_gas_per_block() as f64, + ); + + // Percentiles were specified, so we need to collect reward percentile info + if let Some(percentiles) = &reward_percentiles { + let (block, receipts) = self.cache() + .get_block_and_receipts(header.hash()) + .await + .map_err(Self::Error::from_eth_err)? + .ok_or(EthApiError::InvalidBlockRange)?; + rewards.push( + calculate_reward_percentiles_for_block( + percentiles, + header.gas_used(), + header.base_fee_per_gas().unwrap_or_default(), + block.body().transactions(), + &receipts, + ) + .unwrap_or_default(), + ); + } + } + + // The spec states that `base_fee_per_gas` "[..] includes the next block after the + // newest of the returned range, because this value can be derived from the + // newest block" + // + // The unwrap is safe since we checked earlier that we got at least 1 header. + let last_header = headers.last().expect("is present"); + let mut provider = self + .provider() + .state_by_block_id(last_header.hash().into()) + .map_err(Into::<EthApiError>::into)?; + base_fee_per_gas.push( + base_fee_provider + .next_block_base_fee( + &mut provider, + &last_header.header(), + last_header.timestamp(), + ) + .map_err(Into::<EthApiError>::into)? as u128, + ); + // Same goes for the `base_fee_per_blob_gas`: + // > "[..] includes the next block after the newest of the returned range, because this value can be derived from the newest block. + base_fee_per_blob_gas.push( + last_header + .maybe_next_block_blob_fee( + chain_spec.blob_params_at_timestamp(last_header.timestamp()) + ).unwrap_or_default() + ); + }; + + // Scroll-specific logic: update rewards if the newest_block is not at capacity and tip + // calculation succeeds + let (suggest_tip_cap_result, is_at_capacity) = self + .gas_oracle() + .calculate_suggest_tip_cap( + newest_block, + U256::from(self.inner.min_suggested_priority_fee), + self.inner.payload_size_limit, + ) + .await; + + let reward = match (is_at_capacity, suggest_tip_cap_result) { + (false, Ok(suggest_tip_cap_value)) => { + let suggest_tip_cap = suggest_tip_cap_value.saturating_to::<u128>(); + reward_percentiles.map(|percentiles| { + vec![vec![suggest_tip_cap; percentiles.len()]; block_count as usize] + }) + } + _ => reward_percentiles.map(|_| rewards), + }; + + Ok(FeeHistory { + base_fee_per_gas, + gas_used_ratio, + base_fee_per_blob_gas, + blob_gas_used_ratio, + oldest_block: start_block, + reward, + }) + } + } +}
diff --git reth/crates/scroll/rpc/src/eth/mod.rs scroll-reth/crates/scroll/rpc/src/eth/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..8084177efa234131270a35f587553c8434ada0cc --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/eth/mod.rs @@ -0,0 +1,416 @@ +//! Scroll-Reth `eth_` endpoint implementation. + +use crate::{ + eth::{receipt::ScrollReceiptConverter, transaction::ScrollTxInfoMapper}, + ScrollEthApiError, SequencerClient, +}; +use alloy_primitives::U256; +use eyre::WrapErr; +pub use receipt::ScrollReceiptBuilder; +use reth_evm::ConfigureEvm; +use reth_node_api::{FullNodeComponents, FullNodeTypes, HeaderTy}; +use reth_node_builder::rpc::{EthApiBuilder, EthApiCtx}; +use reth_provider::{BlockReader, ProviderHeader, ProviderTx}; +use reth_rpc::eth::{core::EthApiInner, DevSigner}; +use reth_rpc_convert::{RpcConvert, RpcConverter, RpcTypes, SignableTxRequest}; +use reth_rpc_eth_api::{ + helpers::{ + pending_block::BuildPendingEnv, spec::SignersForApi, AddDevSigners, EthApiSpec, EthState, + LoadFee, LoadState, SpawnBlocking, Trace, + }, + EthApiTypes, FullEthApiServer, RpcNodeCore, RpcNodeCoreExt, +}; +use reth_rpc_eth_types::{error::FromEvmError, EthStateCache, FeeHistoryCache, GasPriceOracle}; +use reth_tasks::{ + pool::{BlockingTaskGuard, BlockingTaskPool}, + TaskSpawner, +}; +use scroll_alloy_network::Scroll; +use std::{fmt, marker::PhantomData, sync::Arc}; + +mod block; +mod call; +mod fee; +mod pending_block; +pub mod receipt; +pub mod transaction; + +/// Adapter for [`EthApiInner`], which holds all the data required to serve core `eth_` API. +pub type EthApiNodeBackend<N, Rpc> = EthApiInner<N, Rpc>; + +/// A helper trait with requirements for [`RpcNodeCore`] to be used in [`ScrollEthApi`]. +pub trait ScrollNodeCore: RpcNodeCore<Provider: BlockReader> {} +impl<T> ScrollNodeCore for T where T: RpcNodeCore<Provider: BlockReader> {} + +/// Scroll-Reth `Eth` API implementation. +/// +/// This type provides the functionality for handling `eth_` related requests. +/// +/// This wraps a default `Eth` implementation, and provides additional functionality where the +/// scroll spec deviates from the default (ethereum) spec, e.g. transaction forwarding to the +/// receipts, additional RPC fields for transaction receipts. +/// +/// This type implements the [`FullEthApi`](reth_rpc_eth_api::helpers::FullEthApi) by implemented +/// all the `Eth` helper traits and prerequisite traits. +#[derive(Clone)] +pub struct ScrollEthApi<N: RpcNodeCore, Rpc: RpcConvert> { + /// Gateway to node's core components. + inner: Arc<ScrollEthApiInner<N, Rpc>>, +} + +impl<N: RpcNodeCore, Rpc: RpcConvert> ScrollEthApi<N, Rpc> { + /// Creates a new [`ScrollEthApi`]. + pub fn new( + eth_api: EthApiNodeBackend<N, Rpc>, + sequencer_client: Option<SequencerClient>, + min_suggested_priority_fee: U256, + payload_size_limit: u64, + propagate_local_transactions: bool, + ) -> Self { + let inner = Arc::new(ScrollEthApiInner { + eth_api, + min_suggested_priority_fee, + payload_size_limit, + sequencer_client, + propagate_local_transactions, + }); + Self { inner } + } +} + +impl<N, Rpc> ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + Rpc: RpcConvert<Primitives = N::Primitives>, +{ + /// Returns a reference to the [`EthApiNodeBackend`]. + pub fn eth_api(&self) -> &EthApiNodeBackend<N, Rpc> { + self.inner.eth_api() + } + + /// Returns the configured sequencer client, if any. + pub fn sequencer_client(&self) -> Option<&SequencerClient> { + self.inner.sequencer_client() + } + + /// Return a builder for the [`ScrollEthApi`]. + pub fn builder() -> ScrollEthApiBuilder { + ScrollEthApiBuilder::new() + } +} + +impl<N, Rpc> EthApiTypes for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + Rpc: RpcConvert<Primitives = N::Primitives>, +{ + type Error = ScrollEthApiError; + type NetworkTypes = Rpc::Network; + type RpcConvert = Rpc; + + fn tx_resp_builder(&self) -> &Self::RpcConvert { + self.inner.eth_api.tx_resp_builder() + } +} + +impl<N, Rpc> RpcNodeCore for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + Rpc: RpcConvert<Primitives = N::Primitives>, +{ + type Primitives = N::Primitives; + type Provider = N::Provider; + type Pool = N::Pool; + type Evm = <N as RpcNodeCore>::Evm; + type Network = <N as RpcNodeCore>::Network; + + #[inline] + fn pool(&self) -> &Self::Pool { + self.inner.eth_api.pool() + } + + #[inline] + fn evm_config(&self) -> &Self::Evm { + self.inner.eth_api.evm_config() + } + + #[inline] + fn network(&self) -> &Self::Network { + self.inner.eth_api.network() + } + + #[inline] + fn provider(&self) -> &Self::Provider { + self.inner.eth_api.provider() + } +} + +impl<N, Rpc> RpcNodeCoreExt for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + Rpc: RpcConvert<Primitives = N::Primitives>, +{ + #[inline] + fn cache(&self) -> &EthStateCache<N::Primitives> { + self.inner.eth_api.cache() + } +} + +impl<N, Rpc> EthApiSpec for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + Rpc: RpcConvert<Primitives = N::Primitives>, +{ + type Transaction = ProviderTx<Self::Provider>; + type Rpc = Rpc::Network; + + #[inline] + fn starting_block(&self) -> U256 { + self.inner.eth_api.starting_block() + } + + #[inline] + fn signers(&self) -> &SignersForApi<Self> { + self.inner.eth_api.signers() + } +} + +impl<N, Rpc> SpawnBlocking for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + Rpc: RpcConvert<Primitives = N::Primitives>, +{ + #[inline] + fn io_task_spawner(&self) -> impl TaskSpawner { + self.inner.eth_api.task_spawner() + } + + #[inline] + fn tracing_task_pool(&self) -> &BlockingTaskPool { + self.inner.eth_api.blocking_task_pool() + } + + #[inline] + fn tracing_task_guard(&self) -> &BlockingTaskGuard { + self.inner.eth_api.blocking_task_guard() + } +} + +impl<N, Rpc> LoadFee for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + ScrollEthApiError: FromEvmError<N::Evm>, + Rpc: RpcConvert<Primitives = N::Primitives, Error = ScrollEthApiError>, +{ + #[inline] + fn gas_oracle(&self) -> &GasPriceOracle<Self::Provider> { + self.inner.eth_api.gas_oracle() + } + + #[inline] + fn fee_history_cache(&self) -> &FeeHistoryCache<ProviderHeader<N::Provider>> { + self.inner.eth_api.fee_history_cache() + } + + async fn suggested_priority_fee(&self) -> Result<U256, Self::Error> { + let min_tip = U256::from(self.inner.min_suggested_priority_fee); + self.inner + .eth_api + .gas_oracle() + .scroll_suggest_tip_cap(min_tip, self.inner.payload_size_limit) + .await + .map_err(Into::into) + } +} + +impl<N, Rpc> LoadState for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + Rpc: RpcConvert<Primitives = N::Primitives>, +{ +} + +impl<N, Rpc> EthState for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + Rpc: RpcConvert<Primitives = N::Primitives>, +{ + #[inline] + fn max_proof_window(&self) -> u64 { + self.inner.eth_api.eth_proof_window() + } +} + +impl<N, Rpc> Trace for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + ScrollEthApiError: FromEvmError<N::Evm>, + Rpc: RpcConvert<Primitives = N::Primitives, Error = ScrollEthApiError>, +{ +} + +impl<N, Rpc> AddDevSigners for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + Rpc: RpcConvert< + Network: RpcTypes<TransactionRequest: SignableTxRequest<ProviderTx<N::Provider>>>, + >, +{ + fn with_dev_accounts(&self) { + *self.inner.eth_api.signers().write() = DevSigner::random_signers(20) + } +} + +impl<N: ScrollNodeCore, Rpc: RpcConvert> fmt::Debug for ScrollEthApi<N, Rpc> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ScrollEthApi").finish_non_exhaustive() + } +} + +/// Container type `ScrollEthApi` +#[allow(missing_debug_implementations)] +pub struct ScrollEthApiInner<N: ScrollNodeCore, Rpc: RpcConvert> { + /// Gateway to node's core components. + pub eth_api: EthApiNodeBackend<N, Rpc>, + /// Sequencer client, configured to forward submitted transactions to sequencer of given Scroll + /// network. + sequencer_client: Option<SequencerClient>, + /// Minimum priority fee + min_suggested_priority_fee: U256, + /// Maximum payload size + payload_size_limit: u64, + /// whether local transactions should be propagated. + propagate_local_transactions: bool, +} + +impl<N: RpcNodeCore, Rpc: RpcConvert> ScrollEthApiInner<N, Rpc> { + /// Returns a reference to the [`EthApiNodeBackend`]. + const fn eth_api(&self) -> &EthApiNodeBackend<N, Rpc> { + &self.eth_api + } + + /// Returns the configured sequencer client, if any. + const fn sequencer_client(&self) -> Option<&SequencerClient> { + self.sequencer_client.as_ref() + } +} + +/// Converter for Scroll RPC types. +pub type ScrollRpcConvert<N, NetworkT> = RpcConverter< + NetworkT, + <N as FullNodeComponents>::Evm, + ScrollReceiptConverter, + (), + ScrollTxInfoMapper<<N as FullNodeTypes>::Provider>, +>; + +/// The default suggested priority fee for the gas price oracle. +pub const DEFAULT_MIN_SUGGESTED_PRIORITY_FEE: u64 = 100; + +/// The default payload size limit in bytes for the sequencer. +pub const DEFAULT_PAYLOAD_SIZE_LIMIT: u64 = 122_880; + +/// A type that knows how to build a [`ScrollEthApi`]. +#[derive(Debug)] +pub struct ScrollEthApiBuilder<NetworkT = Scroll> { + /// Sequencer client, configured to forward submitted transactions to sequencer of given Scroll + /// network. + sequencer_url: Option<String>, + /// Minimum suggested priority fee (tip) + min_suggested_priority_fee: u64, + /// Maximum payload size + payload_size_limit: u64, + /// whether local transactions should be propagated. + propagate_local_transactions: bool, + /// Marker for network types. + _nt: PhantomData<NetworkT>, +} + +impl<NetworkT> Default for ScrollEthApiBuilder<NetworkT> { + fn default() -> Self { + Self { + sequencer_url: None, + min_suggested_priority_fee: DEFAULT_MIN_SUGGESTED_PRIORITY_FEE, + payload_size_limit: DEFAULT_PAYLOAD_SIZE_LIMIT, + propagate_local_transactions: true, + _nt: PhantomData, + } + } +} + +impl<NetworkT> ScrollEthApiBuilder<NetworkT> { + /// Creates a [`ScrollEthApiBuilder`] instance. + pub fn new() -> Self { + Self::default() + } + + /// With a [`SequencerClient`]. + pub fn with_sequencer(mut self, sequencer_url: Option<String>) -> Self { + self.sequencer_url = sequencer_url; + self + } + + /// With minimum suggested priority fee (tip) + pub const fn with_min_suggested_priority_fee(mut self, min: u64) -> Self { + self.min_suggested_priority_fee = min; + self + } + + /// With payload size limit + pub const fn with_payload_size_limit(mut self, limit: u64) -> Self { + self.payload_size_limit = limit; + self + } + + /// With whether local transactions should be propagated. + pub const fn with_propagate_local_transactions( + &mut self, + propagate_local_transactions: bool, + ) -> &mut Self { + self.propagate_local_transactions = propagate_local_transactions; + self + } +} + +impl<N, NetworkT> EthApiBuilder<N> for ScrollEthApiBuilder<NetworkT> +where + N: FullNodeComponents<Evm: ConfigureEvm<NextBlockEnvCtx: BuildPendingEnv<HeaderTy<N::Types>>>>, + NetworkT: RpcTypes, + ScrollRpcConvert<N, NetworkT>: RpcConvert<Network = NetworkT>, + ScrollEthApi<N, ScrollRpcConvert<N, NetworkT>>: + FullEthApiServer<Provider = N::Provider, Pool = N::Pool> + AddDevSigners, +{ + type EthApi = ScrollEthApi<N, ScrollRpcConvert<N, NetworkT>>; + + async fn build_eth_api(self, ctx: EthApiCtx<'_, N>) -> eyre::Result<Self::EthApi> { + let Self { + min_suggested_priority_fee, + payload_size_limit, + sequencer_url, + propagate_local_transactions, + .. + } = self; + let rpc_converter = RpcConverter::new(ScrollReceiptConverter::default()) + .with_mapper(ScrollTxInfoMapper::new(ctx.components.provider().clone())); + + let eth_api = ctx.eth_api_builder().with_rpc_converter(rpc_converter).build_inner(); + + let sequencer_client = if let Some(url) = sequencer_url { + Some( + SequencerClient::new(&url) + .await + .wrap_err_with(|| "Failed to init sequencer client with: {url}")?, + ) + } else { + None + }; + + Ok(ScrollEthApi::new( + eth_api, + sequencer_client, + U256::from(min_suggested_priority_fee), + payload_size_limit, + propagate_local_transactions, + )) + } +}
diff --git reth/crates/scroll/rpc/src/eth/pending_block.rs scroll-reth/crates/scroll/rpc/src/eth/pending_block.rs new file mode 100644 index 0000000000000000000000000000000000000000..21075c77f87c007c9740a3a8055b454d18d9a6fd --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/eth/pending_block.rs @@ -0,0 +1,25 @@ +//! Loads Scroll pending block for an RPC response. + +use crate::{ScrollEthApi, ScrollEthApiError}; +use reth_rpc_eth_api::{ + helpers::{pending_block::PendingEnvBuilder, LoadPendingBlock}, + RpcConvert, RpcNodeCore, +}; +use reth_rpc_eth_types::{error::FromEvmError, PendingBlock}; + +impl<N, Rpc> LoadPendingBlock for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + ScrollEthApiError: FromEvmError<N::Evm>, + Rpc: RpcConvert<Primitives = N::Primitives>, +{ + #[inline] + fn pending_block(&self) -> &tokio::sync::Mutex<Option<PendingBlock<N::Primitives>>> { + self.inner.eth_api.pending_block() + } + + #[inline] + fn pending_env_builder(&self) -> &dyn PendingEnvBuilder<Self::Evm> { + self.inner.eth_api.pending_env_builder() + } +}
diff --git reth/crates/scroll/rpc/src/eth/receipt.rs scroll-reth/crates/scroll/rpc/src/eth/receipt.rs new file mode 100644 index 0000000000000000000000000000000000000000..16d5ac93d60be7cbc7b0388c66137d4b04d77a2e --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/eth/receipt.rs @@ -0,0 +1,99 @@ +//! Loads and formats Scroll receipt RPC response. + +use crate::{ScrollEthApi, ScrollEthApiError}; +use alloy_rpc_types_eth::{Log, TransactionReceipt}; +use reth_primitives_traits::NodePrimitives; +use reth_rpc_convert::{ + transaction::{ConvertReceiptInput, ReceiptConverter}, + RpcConvert, +}; +use reth_rpc_eth_api::{helpers::LoadReceipt, RpcNodeCore}; +use reth_rpc_eth_types::receipt::build_receipt; +use reth_scroll_primitives::{ScrollReceipt, ScrollTransactionSigned}; +use scroll_alloy_consensus::ScrollReceiptEnvelope; +use scroll_alloy_rpc_types::{ScrollTransactionReceipt, ScrollTransactionReceiptFields}; +use std::fmt::Debug; + +impl<N, Rpc> LoadReceipt for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + Rpc: RpcConvert<Primitives = N::Primitives, Error = ScrollEthApiError>, +{ +} + +/// Converter for Scroll receipts. +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct ScrollReceiptConverter; + +impl<N> ReceiptConverter<N> for ScrollReceiptConverter +where + N: NodePrimitives<SignedTx = ScrollTransactionSigned, Receipt = ScrollReceipt>, +{ + type RpcReceipt = ScrollTransactionReceipt; + type Error = ScrollEthApiError; + + fn convert_receipts( + &self, + inputs: Vec<ConvertReceiptInput<'_, N>>, + ) -> Result<Vec<Self::RpcReceipt>, Self::Error> { + let mut receipts = Vec::with_capacity(inputs.len()); + + for input in inputs { + receipts.push(ScrollReceiptBuilder::new(input)?.build()); + } + + Ok(receipts) + } +} + +/// Builds an [`ScrollTransactionReceipt`]. +#[derive(Debug)] +pub struct ScrollReceiptBuilder { + /// Core receipt, has all the fields of an L1 receipt and is the basis for the Scroll receipt. + pub core_receipt: TransactionReceipt<ScrollReceiptEnvelope<Log>>, + /// Additional Scroll receipt fields. + pub scroll_receipt_fields: ScrollTransactionReceiptFields, +} + +impl ScrollReceiptBuilder { + /// Returns a new builder. + pub fn new<N>(input: ConvertReceiptInput<'_, N>) -> Result<Self, ScrollEthApiError> + where + N: NodePrimitives<SignedTx = ScrollTransactionSigned, Receipt = ScrollReceipt>, + { + let core_receipt = + build_receipt(&input, None, |receipt_with_bloom| match input.receipt.as_ref() { + ScrollReceipt::Legacy(_) => { + ScrollReceiptEnvelope::<Log>::Legacy(receipt_with_bloom) + } + ScrollReceipt::Eip2930(_) => { + ScrollReceiptEnvelope::<Log>::Eip2930(receipt_with_bloom) + } + ScrollReceipt::Eip1559(_) => { + ScrollReceiptEnvelope::<Log>::Eip1559(receipt_with_bloom) + } + ScrollReceipt::Eip7702(_) => { + ScrollReceiptEnvelope::<Log>::Eip7702(receipt_with_bloom) + } + ScrollReceipt::L1Message(_) => { + ScrollReceiptEnvelope::<Log>::L1Message(receipt_with_bloom) + } + }); + + let scroll_receipt_fields = + ScrollTransactionReceiptFields { l1_fee: Some(input.receipt.l1_fee().saturating_to()) }; + + Ok(Self { core_receipt, scroll_receipt_fields }) + } + + /// Builds [`ScrollTransactionReceipt`] by combing core (l1) receipt fields and additional + /// Scroll receipt fields. + pub fn build(self) -> ScrollTransactionReceipt { + let Self { core_receipt: inner, scroll_receipt_fields } = self; + + let ScrollTransactionReceiptFields { l1_fee, .. } = scroll_receipt_fields; + + ScrollTransactionReceipt { inner, l1_fee } + } +}
diff --git reth/crates/scroll/rpc/src/eth/transaction.rs scroll-reth/crates/scroll/rpc/src/eth/transaction.rs new file mode 100644 index 0000000000000000000000000000000000000000..d10983e798f2564e4a39ba5b36774dc97449077f --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/eth/transaction.rs @@ -0,0 +1,136 @@ +//! Loads and formats Scroll transaction RPC response. + +use crate::{ScrollEthApi, ScrollEthApiError, SequencerClient}; +use alloy_consensus::transaction::TransactionInfo; +use alloy_primitives::{Bytes, B256}; +use reth_evm::execute::ProviderError; +use reth_provider::ReceiptProvider; +use reth_rpc_convert::RpcConvert; +use reth_rpc_eth_api::{ + helpers::{spec::SignersForRpc, EthTransactions, LoadTransaction}, + try_into_scroll_tx_info, FromEthApiError, RpcNodeCore, TxInfoMapper, +}; +use reth_rpc_eth_types::utils::recover_raw_transaction; +use reth_scroll_primitives::ScrollReceipt; +use reth_transaction_pool::{ + AddedTransactionOutcome, PoolTransaction, TransactionOrigin, TransactionPool, +}; +use scroll_alloy_consensus::{ScrollTransactionInfo, ScrollTxEnvelope}; +use std::fmt::{Debug, Formatter}; + +impl<N, Rpc> EthTransactions for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + Rpc: RpcConvert<Primitives = N::Primitives, Error = ScrollEthApiError>, +{ + fn signers(&self) -> &SignersForRpc<Self::Provider, Self::NetworkTypes> { + self.inner.eth_api.signers() + } + + /// Decodes and recovers the transaction and submits it to the pool. + /// + /// Returns the hash of the transaction. + async fn send_raw_transaction(&self, tx: Bytes) -> Result<B256, Self::Error> { + let recovered = recover_raw_transaction(&tx)?; + let pool_transaction = <Self::Pool as TransactionPool>::Transaction::from_pooled(recovered); + + // submit the transaction to the pool with a `Local` origin + let AddedTransactionOutcome { hash, .. } = self + .pool() + .add_transaction(TransactionOrigin::Local, pool_transaction.clone()) + .await + .map_err(Self::Error::from_eth_err)?; + + // On scroll, transactions are forwarded directly to the sequencer to be included in + // blocks that it builds. + if let Some(client) = self.raw_tx_forwarder() { + tracing::debug!(target: "scroll::rpc::eth", hash = %pool_transaction.hash(), "forwarding raw transaction to sequencer"); + + if self.inner.propagate_local_transactions { + // Forward to remote sequencer RPC asynchronously (fire and forget) + let client = client.clone(); + tokio::spawn(async move { + match client.forward_raw_transaction(&tx).await { + Ok(sequencer_hash) => { + tracing::debug!(target: "scroll::rpc::eth", local_hash=%hash, %sequencer_hash, "successfully forwarded transaction to sequencer"); + } + Err(err) => { + tracing::warn!(target: "scroll::rpc::eth", %err, local_hash=%hash, "failed to forward transaction to sequencer, but transaction is in local pool and will be propagated"); + } + } + }); + } else { + // Forward to remote sequencer RPC synchronously + match client.forward_raw_transaction(&tx).await { + Ok(sequencer_hash) => { + tracing::debug!(target: "scroll::rpc::eth", local_hash=%hash, %sequencer_hash, "successfully forwarded transaction to sequencer"); + } + Err(err) => { + tracing::warn!(target: "scroll::rpc::eth", %err, local_hash=%hash, "failed to forward transaction to sequencer"); + return Err(ScrollEthApiError::Sequencer(err)); + } + } + } + } + + Ok(hash) + } +} + +impl<N, Rpc> LoadTransaction for ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + Rpc: RpcConvert<Primitives = N::Primitives, Error = ScrollEthApiError>, +{ +} + +impl<N, Rpc> ScrollEthApi<N, Rpc> +where + N: RpcNodeCore, + Rpc: RpcConvert<Primitives = N::Primitives>, +{ + /// Returns the [`SequencerClient`] if one is set. + pub fn raw_tx_forwarder(&self) -> Option<SequencerClient> { + self.inner.sequencer_client.clone() + } +} + +/// Scroll implementation of [`TxInfoMapper`]. +/// +/// Receipt is fetched to extract the `l1_fee` for all transactions but L1 messages. +pub struct ScrollTxInfoMapper<Provider>(Provider); + +impl<Provider: Clone> Clone for ScrollTxInfoMapper<Provider> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<Provider: Debug> Debug for ScrollTxInfoMapper<Provider> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ScrollTxInfoMapper").finish() + } +} + +impl<Provider> ScrollTxInfoMapper<Provider> { + /// Creates [`ScrollTxInfoMapper`] that uses [`ReceiptProvider`] borrowed from given `eth_api`. + pub const fn new(provider: Provider) -> Self { + Self(provider) + } +} + +impl<Provider> TxInfoMapper<&ScrollTxEnvelope> for ScrollTxInfoMapper<Provider> +where + Provider: ReceiptProvider<Receipt = ScrollReceipt>, +{ + type Out = ScrollTransactionInfo; + type Err = ProviderError; + + fn try_map( + &self, + tx: &ScrollTxEnvelope, + tx_info: TransactionInfo, + ) -> Result<Self::Out, ProviderError> { + try_into_scroll_tx_info(&self.0, tx, tx_info) + } +}
diff --git reth/crates/scroll/rpc/src/lib.rs scroll-reth/crates/scroll/rpc/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..76ecfbec7b6bfea71f8f243143b8493cefe4cedc --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/lib.rs @@ -0,0 +1,17 @@ +//! Scroll-Reth RPC support. + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +pub mod error; +pub mod eth; +pub mod sequencer; + +pub use error::{ScrollEthApiError, SequencerClientError}; +pub use eth::{ScrollEthApi, ScrollReceiptBuilder}; +pub use sequencer::SequencerClient;
diff --git reth/crates/scroll/rpc/src/sequencer.rs scroll-reth/crates/scroll/rpc/src/sequencer.rs new file mode 100644 index 0000000000000000000000000000000000000000..637525460d4c5b2ed17a19c1ee833674db89a187 --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/sequencer.rs @@ -0,0 +1,201 @@ +//! Helpers for scroll specific RPC implementations. + +use crate::SequencerClientError; +use alloy_json_rpc::{RpcRecv, RpcSend}; +use alloy_primitives::{hex, B256}; +use alloy_rpc_client::{BuiltInConnectionString, ClientBuilder, RpcClient as Client}; +use alloy_transport_http::Http; +use std::{str::FromStr, sync::Arc}; +use thiserror::Error; +use tracing::warn; + +/// Sequencer client error +#[derive(Error, Debug)] +pub enum Error { + /// Invalid scheme + #[error("Invalid scheme of sequencer url: {0}")] + InvalidScheme(String), + /// Invalid url + #[error("Invalid sequencer url: {0}")] + InvalidUrl(String), + /// Establishing a connection to the sequencer endpoint resulted in an error. + #[error("Failed to connect to sequencer: {0}")] + TransportError( + #[from] + #[source] + alloy_transport::TransportError, + ), + /// Reqwest failed to init client + #[error("Failed to init reqwest client for sequencer: {0}")] + ReqwestError( + #[from] + #[source] + reqwest::Error, + ), +} + +/// A client to interact with a Sequencer +#[derive(Debug, Clone)] +pub struct SequencerClient { + inner: Arc<SequencerClientInner>, +} + +impl SequencerClient { + /// Creates a new [`SequencerClient`] for the given URL. + /// + /// If the URL is a websocket endpoint we connect a websocket instance. + pub async fn new(sequencer_endpoint: impl Into<String>) -> Result<Self, Error> { + let sequencer_endpoint = sequencer_endpoint.into(); + let endpoint = BuiltInConnectionString::from_str(&sequencer_endpoint)?; + if let BuiltInConnectionString::Http(url) = endpoint { + let client = reqwest::Client::builder() + // we force use tls to prevent native issues + .use_rustls_tls() + .build()?; + Self::with_http_client(url, client) + } else { + let client = ClientBuilder::default().connect_with(endpoint).await?; + let inner = SequencerClientInner { sequencer_endpoint, client }; + Ok(Self { inner: Arc::new(inner) }) + } + } + + /// Creates a new [`SequencerClient`] with http transport with the given http client. + pub fn with_http_client( + sequencer_endpoint: impl Into<String>, + client: reqwest::Client, + ) -> Result<Self, Error> { + let sequencer_endpoint: String = sequencer_endpoint.into(); + let url = sequencer_endpoint + .parse() + .map_err(|_| Error::InvalidUrl(sequencer_endpoint.clone()))?; + + let http_client = Http::with_client(client, url); + let is_local = http_client.guess_local(); + let client = ClientBuilder::default().transport(http_client, is_local); + + let inner = SequencerClientInner { sequencer_endpoint, client }; + Ok(Self { inner: Arc::new(inner) }) + } + + /// Returns the network of the client + pub fn endpoint(&self) -> &str { + &self.inner.sequencer_endpoint + } + + /// Returns the client + pub fn client(&self) -> &Client { + &self.inner.client + } + + /// Sends a [`alloy_rpc_client::RpcCall`] request to the sequencer endpoint. + async fn send_rpc_call<Params: RpcSend, Resp: RpcRecv>( + &self, + method: &str, + params: Params, + ) -> Result<Resp, SequencerClientError> { + let resp = + self.client().request::<Params, Resp>(method.to_string(), params).await.inspect_err( + |err| { + warn!( + target: "scroll::rpc::sequencer", + %err, + "HTTP request to sequencer failed", + ); + }, + )?; + Ok(resp) + } + + /// Forwards a transaction to the sequencer endpoint. + pub async fn forward_raw_transaction(&self, tx: &[u8]) -> Result<B256, SequencerClientError> { + let rlp_hex = hex::encode_prefixed(tx); + let tx_hash = + self.send_rpc_call("eth_sendRawTransaction", (rlp_hex,)).await.inspect_err(|err| { + warn!( + target: "scroll::rpc::eth", + %err, + "Failed to forward transaction to sequencer", + ); + })?; + + Ok(tx_hash) + } +} + +#[derive(Debug)] +struct SequencerClientInner { + /// The endpoint of the sequencer + sequencer_endpoint: String, + /// The client + client: Client, +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::U64; + + #[tokio::test] + async fn test_http_body_str() { + let client = SequencerClient::new("http://localhost:8545").await.unwrap(); + + let request = client + .client() + .make_request("eth_getBlockByNumber", (U64::from(10),)) + .serialize() + .unwrap() + .take_request(); + let body = request.get(); + + assert_eq!( + body, + r#"{"method":"eth_getBlockByNumber","params":["0xa"],"id":0,"jsonrpc":"2.0"}"# + ); + + let request = client + .client() + .make_request("eth_sendRawTransaction", format!("0x{}", hex::encode("abcd"))) + .serialize() + .unwrap() + .take_request(); + let body = request.get(); + + assert_eq!( + body, + r#"{"method":"eth_sendRawTransaction","params":"0x61626364","id":1,"jsonrpc":"2.0"}"# + ); + } + + #[tokio::test] + #[ignore = "Start if WS is reachable at ws://localhost:8546"] + async fn test_ws_body_str() { + let client = SequencerClient::new("ws://localhost:8546").await.unwrap(); + + let request = client + .client() + .make_request("eth_getBlockByNumber", (U64::from(10),)) + .serialize() + .unwrap() + .take_request(); + let body = request.get(); + + assert_eq!( + body, + r#"{"method":"eth_getBlockByNumber","params":["0xa"],"id":0,"jsonrpc":"2.0"}"# + ); + + let request = client + .client() + .make_request("eth_sendRawTransaction", format!("0x{}", hex::encode("abcd"))) + .serialize() + .unwrap() + .take_request(); + let body = request.get(); + + assert_eq!( + body, + r#"{"method":"eth_sendRawTransaction","params":"0x61626364","id":1,"jsonrpc":"2.0"}"# + ); + } +}
diff --git reth/crates/scroll/trie/Cargo.toml scroll-reth/crates/scroll/trie/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..4154ad181afe452c66590e79ed870c82e7d70aa6 --- /dev/null +++ scroll-reth/crates/scroll/trie/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "reth-scroll-trie" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# reth +reth-trie.workspace = true + +# alloy +alloy-primitives.workspace = true +alloy-trie = { workspace = true, features = ["serde"] } + +# misc +poseidon-bn254 = { workspace = true, features = ["bn254"] } +tracing.workspace = true
diff --git reth/crates/scroll/trie/README.md scroll-reth/crates/scroll/trie/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8645b5efadd58f51d8d04146614741788a95febd --- /dev/null +++ scroll-reth/crates/scroll/trie/README.md @@ -0,0 +1,5 @@ +# scroll-trie + +Fast binary Merkle-Patricia Trie (zktrie) state root calculator and proof generator for prefix-sorted bits. + +Please see the specification of zktrie [here](assets/zktrie.md). \ No newline at end of file
diff --git reth/crates/scroll/trie/assets/arch.png scroll-reth/crates/scroll/trie/assets/arch.png new file mode 100644 index 0000000000000000000000000000000000000000..aca3f8d0c5ce4eedb5d40b8dedb39d226e57efbe Binary files /dev/null and scroll-reth/crates/scroll/trie/assets/arch.png differ
diff --git reth/crates/scroll/trie/assets/deletion.png scroll-reth/crates/scroll/trie/assets/deletion.png new file mode 100644 index 0000000000000000000000000000000000000000..6c699226e0996a30199a3ff4d2441700390cb8e2 Binary files /dev/null and scroll-reth/crates/scroll/trie/assets/deletion.png differ
diff --git reth/crates/scroll/trie/assets/insertion.png scroll-reth/crates/scroll/trie/assets/insertion.png new file mode 100644 index 0000000000000000000000000000000000000000..942338a07f2ea46e33fc899fd222b6ae6671f5e4 Binary files /dev/null and scroll-reth/crates/scroll/trie/assets/insertion.png differ
diff --git reth/crates/scroll/trie/assets/zktrie.md scroll-reth/crates/scroll/trie/assets/zktrie.md new file mode 100644 index 0000000000000000000000000000000000000000..b86e1a016ab8bae8e13f7e1b9f7fe1d29bfc7711 --- /dev/null +++ scroll-reth/crates/scroll/trie/assets/zktrie.md @@ -0,0 +1,186 @@ +# zkTrie Spec + +## 1. Tree Structure + +<figure> +<img src="https://raw.githubusercontent.com/scroll-tech/reth/refs/heads/scroll/crates/scroll/trie/assets/arch.png" alt="zkTrie Structure" style="width:80%"> +<figcaption align = "center"><b>Figure 1. zkTrie Structure</b></figcaption> +</figure> + +In essence, zkTrie is a sparse binary Merkle Patricia Trie, depicted in the above figure. +Before diving into the Sparse Binary Merkle Patricia Trie, let's briefly touch on Merkle Trees and Patricia Tries. +* **Merkle Tree**: A Merkle Tree is a tree where each leaf node represents a hash of a data block, and each non-leaf node represents the hash of its child nodes. +* **Patricia Trie**: A Patricia Trie is a type of radix tree or compressed trie used to store key-value pairs efficiently. It encodes the nodes with same prefix of the key to share the common path, where the path is determined by the value of the node key. + +As illustrated in the Figure 1, there are three types of nodes in the zkTrie. +- Parent Node (type: 0): Given the zkTrie is a binary tree, a parent node has two children. +- Leaf Node (type: 1): A leaf node holds the data of a key-value pair. +- Empty Node (type: 2): An empty node is a special type of node, indicating the sub-trie that shares the same prefix is empty. + +In zkTrie, we use Poseidon hash to compute the node hash because it's more friendly and efficient to prove it in the zk circuit. + +## 2. Tree Construction + +Given a key-value pair, we first compute a *secure key* for the corresponding leaf node by hashing the original key (i.e., account address and storage key) using the Poseidon hash function. This can make the key uniformly distributed over the key space. The node key hashing method is described in the [Node Hashing](#3-node-hashing) section below. + +We then encode the path of a new leaf node by traversing the secure key from Least Significant Bit (LSB) to the Most Significant Bit (MSB). At each step, if the bit is 0, we will traverse to the left child; otherwise, traverse to the right child. + +We limit the maximum depth of zkTrie to 248, meaning that the tree will only traverse the lower 248 bits of the key. This is because the secure key space is a finite field used by Poseidon hash that doesn't occupy the full range of power of 2. This leads to an ambiguous bit representation of the key in a finite field and thus causes a soundness issue in the zk circuit. But if we truncate the key to lower 248 bits, the key space can fully occupy the range of $2^{248}$ and won't have the ambiguity in the bit representation. + +We also apply an optimization to reduce the tree depth by contracting a subtree that has only one leaf node to a single leaf node. For example, in the Figure 1, the tree has three nodes in total, with keys `0100`, `0010`, and `1010`. Because there is only one node that has key with suffix `00`, the leaf node for key `0100` only traverses the suffix `00` and doesn't fully expand its key which would have resulted in depth of 4. + +## 3. Node Hashing + +In this section, we will describe how leaf secure key and node merkle hash are computed. We use Poseidon hash in both hashing computation, denoted as `h` in the doc below. + +<aside> +💡 Note: We use `init_state = 0` in the Poseidon hash function for all use cases in the zkTrie. +</aside> + +### 3.1 Empty Node + +The node hash of an empty node is 0. + +### 3.2 Parent Node + +The parent node hash is computed as follows + +```go +parentNodeHash = h(leftChildHash, rightChildHash) +``` + +### 3.3 Leaf Node + +The node hash of a leaf node is computed as follows + +```go +leafNodeHash = h(h(1, nodeKey), valueHash) +``` + +The leaf node can hold two types of values: Ethereum accounts and storage key-value pairs. Next, we will describe how the node key and value hash are computed for each leaf node type. + +#### Ethereum Account Leaf Node +For an Ethereum Account Leaf Node, it consists of an Ethereum address and a state account struct. The secure key is derived from the Ethereum address. +``` +address[0:20] (20 bytes in big-endian) +valHi = address[0:16] +valLo = address[16:20] * 2^96 (padding 12 bytes of 0 at the end) +nodeKey = h(valHi, valLo) +``` + +A state account struct in the Scroll consists of the following fields (`Fr` indicates the finite field used in Poseidon hash and is a 254-bit value) + +- `Nonce`: u64 +- `Balance`: u256, but treated as Fr +- `StorageRoot`: Fr +- `KeccakCodeHash`: u256 +- `PoseidonCodeHash`: Fr +- `CodeSize`: u64 + +Before computing the value hash, the state account is first marshaled into a list of `u256` values. The marshaling scheme is + +``` +(The following scheme assumes the big-endian encoding) +[0:32] (bytes in big-endian) + [0:16] Reserved with all 0 + [16:24] CodeSize, uint64 in big-endian + [24:32] Nonce, uint64 in big-endian +[32:64] Balance +[64:96] StorageRoot +[96:128] KeccakCodeHash +[128:160] PoseidonCodehash +(total 160 bytes) +``` + +The marshal function also returns a `flag` value along with a vector of `u256` values. The `flag` is a bitmap that indicates whether a `u256` value CANNOT be treated as a field element (Fr). The `flag` value for state account is 8, shown below. + +``` ++--------------------+---------+------+----------+----------+ +| 0 | 1 | 2 | 3 | 4 | (index) ++--------------------+---------+------+----------+----------+ +| nonce||codesize||0 | balance | root | keccak | poseidon | (u256) ++--------------------+---------+------+----------+----------+ +| 0 | 0 | 0 | 1 | 0 | (flag bits) ++--------------------+---------+------+----------+----------+ +(LSB) (MSB) +``` + +The value hash is computed in two steps: +1. Convert the value that cannot be represented as a field element of the Poseidon hash to the field element. +2. Combine field elements in a binary tree structure till the tree root is treated as the value hash. + +In the first step, when the bit in the `flag` is 1 indicating the `u256` value that cannot be treated as a field element, we split the value into a high-128bit value and a low-128bit value, and then pass them to a Poseidon hash to derive a field element value, `h(valueHi, valueLo)`. + +Based on the definition, the value hash of the state account is computed as follows. + +``` +valueHash = +h( + h( + h(nonce||codesize||0, balance), + h( + storageRoot, + h(keccakCodeHash[0:16], keccakCodeHash[16:32]), // convert Keccak codehash to a field element + ), + ), + poseidonCodeHash, +) +``` + +#### Storage Leaf Node + +For a Storage Leaf Node, it is a key-value pair, which both are a `u256` value. The secure key of this leaf node is derived from the storage key. + +``` +storageKey[0:32] (32 bytes in big-endian) +valHi = storageKey[0:16] +valLo = storageKey[16:32] +nodeKey = h(valHi, valLo) +``` + +The storage value is a `u256` value. The `flag` for the storage value is 1, showed below. + +``` ++--------------+ +| 0 | (index) ++--------------+ +| storageValue | (u256) ++--------------+ +| 1 | (flag bits) ++--------------+ +``` + +The value hash is computed as follows + +```go +valueHash = h(storageValue[0:16], storageValue[16:32]) +``` + +## 4. Tree Operations + +### 4.1 Insertion + +<figure> +<img src="https://raw.githubusercontent.com/scroll-tech/reth/refs/heads/scroll/crates/scroll/trie/assets/insertion.png" alt="zkTrie Structure" style="width:80%"> +<figcaption align = "center"><b>Figure 2. Insert a new leaf node to zkTrie</b></figcaption> +</figure> + +When we insert a new leaf node to the existing zkTrie, there could be two cases illustrated in the Figure 2. + +1. When traversing the path of the node key, it reaches an empty node (Figure 2(b)). In this case, we just need to replace this empty node by this leaf node and backtrace the path to update the merkle hash of parent nodes till the root. +2. When traversing the path of the node key, it reaches another leaf node `b` (Figure 2(c)). In this case, we need to push down the existing leaf node `b` until the next bit in the node keys of two leaf nodes differs. At each push-down step, we need to insert an empty sibling node when necessary. When we reach the level where the bits differ, we then place two leaf nodes `b` and `c` as the left child and the right child depending on their bits. At last, we backtrace the path and update the merkle hash of all parent nodes. + +### 4.2 Deletion + +<figure> +<img src="https://raw.githubusercontent.com/scroll-tech/reth/refs/heads/scroll/crates/scroll/trie/assets/deletion.png" alt="zkTrie Structure" style="width:80%"> +<figcaption align = "center"><b>Figure 3. Delete a leaf node from the zkTrie</b></figcaption> +</figure> + + +The deletion of a leaf node is similar to the insertion. There are two cases illustrated in the Figure 3. + +1. The sibling node of to-be-deleted leaf node is a parent node (Figure 3(b)). In this case, we can just replace the node `a` by an empty node and update the node hash of its ancestors till the root node. +2. The node of to-be-deleted leaf node is a leaf node (Figure 3(c)). Similarly, we first replace the leaf node by an empty node and start to contract its sibling node upwards until its sibling node is not an empty node. For example, in Figure 3(c), we first replace the leaf node `b` by an empty node. During the contraction, since the sibling of node `c` now becomes an empty node, we move node `c` one level upward to replace its parent node. The new sibling of node `c`, node `e`, is still an empty node. So again we move node `c` upward. Now that the sibling of node `c` is node `a`, the deletion process is finished. + +Note that the sibling of a leaf node in a valid zkTrie cannot be an empty node. Otherwise, we should always prune the subtree and move the leaf node upwards.
diff --git reth/crates/scroll/trie/src/branch.rs scroll-reth/crates/scroll/trie/src/branch.rs new file mode 100644 index 0000000000000000000000000000000000000000..e6b433913d53e71f2db801ae4c89b3a6f975041c --- /dev/null +++ scroll-reth/crates/scroll/trie/src/branch.rs @@ -0,0 +1,155 @@ +use super::{ + BRANCH_NODE_LBRB_DOMAIN, BRANCH_NODE_LBRT_DOMAIN, BRANCH_NODE_LTRB_DOMAIN, + BRANCH_NODE_LTRT_DOMAIN, +}; +use alloy_primitives::{hex, B256}; +use alloy_trie::TrieMask; +use core::{fmt, ops::Range, slice::Iter}; +use poseidon_bn254::{hash_with_domain, Fr, PrimeField}; + +#[allow(unused_imports)] +use alloc::vec::Vec; + +/// The range of valid child indexes. +pub(crate) const CHILD_INDEX_RANGE: Range<u8> = 0..2; + +/// A trie mask to extract the two child indexes from a branch node. +pub(crate) const CHILD_INDEX_MASK: TrieMask = TrieMask::new(0b11); + +/// A reference to branch node and its state mask. +/// NOTE: The stack may contain more items that specified in the state mask. +#[derive(Clone)] +pub(crate) struct BranchNodeRef<'a> { + /// Reference to the collection of hash nodes. + /// NOTE: The referenced stack might have more items than the number of children + /// for this node. We should only ever access items starting from + /// [`BranchNodeRef::first_child_index`]. + pub stack: &'a [B256], + /// Reference to bitmask indicating the presence of children at + /// the respective nibble positions. + pub state_mask: TrieMask, +} + +impl fmt::Debug for BranchNodeRef<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("BranchNodeRef") + .field("stack", &self.stack.iter().map(hex::encode).collect::<Vec<_>>()) + .field("state_mask", &self.state_mask) + .field("first_child_index", &self.first_child_index()) + .finish() + } +} + +impl<'a> BranchNodeRef<'a> { + /// Create a new branch node from the stack of nodes. + #[inline] + pub(crate) const fn new(stack: &'a [B256], state_mask: TrieMask) -> Self { + Self { stack, state_mask } + } + + /// Returns the stack index of the first child for this node. + /// + /// # Panics + /// + /// If the stack length is less than number of children specified in state mask. + /// Means that the node is in inconsistent state. + #[inline] + pub(crate) fn first_child_index(&self) -> usize { + self.stack + .len() + .checked_sub((self.state_mask & CHILD_INDEX_MASK).count_ones() as usize) + .expect("branch node stack is in inconsistent state") + } + + #[inline] + fn children(&self) -> impl Iterator<Item = (u8, Option<&B256>)> + '_ { + BranchChildrenIter::new(self) + } + + /// Given the hash mask of children, return an iterator over stack items + /// that match the mask. + #[inline] + pub(crate) fn child_hashes(&self, hash_mask: TrieMask) -> impl Iterator<Item = B256> + '_ { + self.children() + .filter_map(|(i, c)| c.map(|c| (i, c))) + .filter(move |(index, _)| hash_mask.is_bit_set(*index)) + .map(|(_, child)| B256::from_slice(&child[..])) + } + + pub(crate) fn hash(&self) -> B256 { + let mut children_iter = self.children(); + + let left_child = children_iter + .next() + .map(|(_, c)| *c.unwrap_or_default()) + .expect("branch node has two children"); + let left_child = + Fr::from_repr_vartime(left_child.0).expect("left child is a valid field element"); + let right_child = children_iter + .next() + .map(|(_, c)| *c.unwrap_or_default()) + .expect("branch node has two children"); + let right_child = + Fr::from_repr_vartime(right_child.0).expect("right child is a valid field element"); + + hash_with_domain(&[left_child, right_child], self.hashing_domain()).to_repr().into() + } + + fn hashing_domain(&self) -> Fr { + match *self.state_mask { + 0b1011 => BRANCH_NODE_LBRT_DOMAIN, + 0b1111 => BRANCH_NODE_LTRT_DOMAIN, + 0b0111 => BRANCH_NODE_LTRB_DOMAIN, + 0b0011 => BRANCH_NODE_LBRB_DOMAIN, + _ => unreachable!("invalid branch node state mask"), + } + } +} + +/// Iterator over branch node children. +#[derive(Debug)] +struct BranchChildrenIter<'a> { + range: Range<u8>, + state_mask: TrieMask, + stack_iter: Iter<'a, B256>, +} + +impl<'a> BranchChildrenIter<'a> { + /// Create new iterator over branch node children. + fn new(node: &BranchNodeRef<'a>) -> Self { + Self { + range: CHILD_INDEX_RANGE, + state_mask: node.state_mask, + stack_iter: node.stack[node.first_child_index()..].iter(), + } + } +} + +impl<'a> Iterator for BranchChildrenIter<'a> { + type Item = (u8, Option<&'a B256>); + + #[inline] + fn next(&mut self) -> Option<Self::Item> { + let i = self.range.next()?; + let value = self + .state_mask + .is_bit_set(i) + .then(|| unsafe { self.stack_iter.next().unwrap_unchecked() }); + Some((i, value)) + } + + #[inline] + fn size_hint(&self) -> (usize, Option<usize>) { + let len = self.len(); + (len, Some(len)) + } +} + +impl core::iter::FusedIterator for BranchChildrenIter<'_> {} + +impl ExactSizeIterator for BranchChildrenIter<'_> { + #[inline] + fn len(&self) -> usize { + self.range.len() + } +}
diff --git reth/crates/scroll/trie/src/constants.rs scroll-reth/crates/scroll/trie/src/constants.rs new file mode 100644 index 0000000000000000000000000000000000000000..67bd414f835834ecca93f29497a396384c87f84e --- /dev/null +++ scroll-reth/crates/scroll/trie/src/constants.rs @@ -0,0 +1,4 @@ +use alloy_primitives::B256; + +/// The root hash of an empty binary Merle Patricia trie. +pub const EMPTY_ROOT_HASH: B256 = B256::ZERO;
diff --git reth/crates/scroll/trie/src/hash_builder.rs scroll-reth/crates/scroll/trie/src/hash_builder.rs new file mode 100644 index 0000000000000000000000000000000000000000..60bdf14087f523e9857e82d884fb034599ceb6d8 --- /dev/null +++ scroll-reth/crates/scroll/trie/src/hash_builder.rs @@ -0,0 +1,568 @@ +use crate::{ + branch::{BranchNodeRef, CHILD_INDEX_MASK}, + constants::EMPTY_ROOT_HASH, + leaf::HashLeaf, + sub_tree::SubTreeRef, +}; +use alloy_primitives::{map::HashMap, B256}; +use alloy_trie::{ + hash_builder::{HashBuilderValue, HashBuilderValueRef}, + nodes::LeafNodeRef, + proof::{ProofNodes, ProofRetainer}, + BranchNodeCompact, Nibbles, TrieMask, +}; +use core::cmp; +use tracing::trace; + +#[derive(Debug, Default)] +#[allow(missing_docs)] +pub struct HashBuilder { + pub key: Nibbles, + pub value: HashBuilderValue, + pub stack: Vec<B256>, + + // TODO(scroll): Introduce terminator / leaf masks + pub state_masks: Vec<TrieMask>, + pub tree_masks: Vec<TrieMask>, + pub hash_masks: Vec<TrieMask>, + + pub stored_in_database: bool, + + pub updated_branch_nodes: Option<HashMap<Nibbles, BranchNodeCompact>>, + pub proof_retainer: Option<ProofRetainer>, +} + +impl HashBuilder { + /// Enables the Hash Builder to store updated branch nodes. + /// + /// Call [`HashBuilder::split`] to get the updates to branch nodes. + pub fn with_updates(mut self, retain_updates: bool) -> Self { + self.set_updates(retain_updates); + self + } + + /// Enable specified proof retainer. + pub fn with_proof_retainer(mut self, retainer: ProofRetainer) -> Self { + self.proof_retainer = Some(retainer); + self + } + + /// Enables the Hash Builder to store updated branch nodes. + /// + /// Call [`HashBuilder::split`] to get the updates to branch nodes. + pub fn set_updates(&mut self, retain_updates: bool) { + if retain_updates { + self.updated_branch_nodes = Some(HashMap::default()); + } + } + + /// Splits the [`HashBuilder`] into a [`HashBuilder`] and hash builder updates. + pub fn split(mut self) -> (Self, HashMap<Nibbles, BranchNodeCompact>) { + let updates = self.updated_branch_nodes.take(); + (self, updates.unwrap_or_default()) + } + + /// Take and return retained proof nodes. + pub fn take_proof_nodes(&mut self) -> ProofNodes { + self.proof_retainer.take().map(ProofRetainer::into_proof_nodes).unwrap_or_default() + } + + /// The number of total updates accrued. + /// Returns `0` if [`Self::with_updates`] was not called. + pub fn updates_len(&self) -> usize { + self.updated_branch_nodes.as_ref().map(|u| u.len()).unwrap_or(0) + } + + /// Print the current stack of the Hash Builder. + pub fn print_stack(&self) { + println!("============ STACK ==============="); + for item in &self.stack { + println!("{}", alloy_primitives::hex::encode(item)); + } + println!("============ END STACK ==============="); + } + + /// Adds a new leaf element and its value to the trie hash builder. + pub fn add_leaf(&mut self, key: Nibbles, value: &[u8]) { + assert!(key > self.key, "add_leaf key {:?} self.key {:?}", key, self.key); + if !self.key.is_empty() { + self.update(&key); + } + self.set_key_value(key, HashBuilderValueRef::Bytes(value)); + } + + /// Adds a new branch element and its hash to the trie hash builder. + pub fn add_branch(&mut self, key: Nibbles, value: B256, stored_in_database: bool) { + assert!( + key > self.key || (self.key.is_empty() && key.is_empty()), + "add_branch key {:?} self.key {:?}", + key, + self.key + ); + if !self.key.is_empty() { + self.update(&key); + } else if key.is_empty() { + self.stack.push(value); + } + self.set_key_value(key, HashBuilderValueRef::Hash(&value)); + self.stored_in_database = stored_in_database; + } + + /// Returns the current root hash of the trie builder. + pub fn root(&mut self) -> B256 { + // Clears the internal state + if !self.key.is_empty() { + self.update(&Nibbles::default()); + self.key.clear(); + self.value.clear(); + } + let root = self.current_root(); + if root == EMPTY_ROOT_HASH { + if let Some(proof_retainer) = self.proof_retainer.as_mut() { + proof_retainer.retain(&Nibbles::default(), &[]) + } + } + root + } + + #[inline] + fn set_key_value(&mut self, key: Nibbles, value: HashBuilderValueRef<'_>) { + self.log_key_value("old value"); + self.key = key; + self.value.set_from_ref(value); + self.log_key_value("new value"); + } + + fn log_key_value(&self, msg: &str) { + trace!(target: "trie::hash_builder", + key = ?self.key, + value = ?self.value, + "{msg}", + ); + } + + fn current_root(&self) -> B256 { + if let Some(node_ref) = self.stack.last() { + let mut root = *node_ref; + root.reverse(); + root + } else { + EMPTY_ROOT_HASH + } + } + + /// Given a new element, it appends it to the stack and proceeds to loop through the stack state + /// and convert the nodes it can into branch / extension nodes and hash them. This ensures + /// that the top of the stack always contains the merkle root corresponding to the trie + /// built so far. + fn update(&mut self, succeeding: &Nibbles) { + let mut build_extensions = false; + // current / self.key is always the latest added element in the trie + let mut current = self.key.clone(); + debug_assert!(!current.is_empty()); + + trace!(target: "trie::hash_builder", ?current, ?succeeding, "updating merkle tree"); + + let mut i = 0usize; + loop { + let _span = tracing::trace_span!(target: "trie::hash_builder", "loop", i, ?current, build_extensions).entered(); + + let preceding_exists = !self.state_masks.is_empty(); + let preceding_len = self.state_masks.len().saturating_sub(1); + + let common_prefix_len = succeeding.common_prefix_length(current.as_slice()); + let len = cmp::max(preceding_len, common_prefix_len); + assert!(len < current.len(), "len {} current.len {}", len, current.len()); + + trace!( + target: "trie::hash_builder", + ?len, + ?common_prefix_len, + ?preceding_len, + preceding_exists, + "prefix lengths after comparing keys" + ); + + // Adjust the state masks for branch calculation + let extra_digit = current[len]; + if self.state_masks.len() <= len { + let new_len = len + 1; + trace!(target: "trie::hash_builder", new_len, old_len = self.state_masks.len(), "scaling state masks to fit"); + self.state_masks.resize(new_len, TrieMask::default()); + } + self.state_masks[len] |= TrieMask::from_nibble(extra_digit); + trace!( + target: "trie::hash_builder", + ?extra_digit, + groups = ?self.state_masks, + ); + + // Adjust the tree masks for exporting to the DB + if self.tree_masks.len() < current.len() { + self.resize_masks(current.len()); + } + + let mut len_from = len; + if !succeeding.is_empty() || preceding_exists { + len_from += 1; + } + trace!(target: "trie::hash_builder", "skipping {len_from} nibbles"); + + // The key without the common prefix + let short_node_key = current.slice(len_from..); + trace!(target: "trie::hash_builder", ?short_node_key); + + // Concatenate the 2 nodes together + if !build_extensions { + match self.value.as_ref() { + HashBuilderValueRef::Bytes(leaf_value) => { + // TODO(scroll): Replace with terminator masks + // Set the terminator mask for the leaf node + self.state_masks[len] |= TrieMask::new(0b100 << extra_digit); + let leaf_node = LeafNodeRef::new(&current, leaf_value); + let leaf_hash = leaf_node.hash_leaf(); + trace!( + target: "trie::hash_builder", + ?leaf_node, + ?leaf_hash, + "pushing leaf node", + ); + self.stack.push(leaf_hash); + // self.retain_proof_from_stack(&current.slice(..len_from)); + } + HashBuilderValueRef::Hash(hash) => { + trace!(target: "trie::hash_builder", ?hash, "pushing branch node hash"); + self.stack.push(*hash); + + if self.stored_in_database { + self.tree_masks[current.len() - 1] |= TrieMask::from_nibble( + current + .last() + .expect("must have at least a single bit in the current key"), + ); + } + self.hash_masks[current.len() - 1] |= TrieMask::from_nibble( + current + .last() + .expect("must have at least a single bit in the current key"), + ); + + build_extensions = true; + } + } + } + + if build_extensions && !short_node_key.is_empty() { + self.update_masks(&current, len_from); + let stack_last = self.stack.pop().expect("there should be at least one stack item"); + let sub_tree = SubTreeRef::new(&short_node_key, &stack_last); + let sub_tree_root = sub_tree.root(); + + trace!( + target: "trie::hash_builder", + ?short_node_key, + ?sub_tree_root, + "pushing subtree root", + ); + self.stack.push(sub_tree_root); + // self.retain_proof_from_stack(&current.slice(..len_from)); + self.resize_masks(len_from); + } + + if preceding_len <= common_prefix_len && !succeeding.is_empty() { + trace!(target: "trie::hash_builder", "no common prefix to create branch nodes from, returning"); + return; + } + + // Insert branch nodes in the stack + if !succeeding.is_empty() || preceding_exists { + // Pushes the corresponding branch node to the stack + let children = self.push_branch_node(&current, len); + // Need to store the branch node in an efficient format outside of the hash builder + self.store_branch_node(&current, len, children); + } + + self.state_masks.resize(len, TrieMask::default()); + self.resize_masks(len); + + if preceding_len == 0 { + trace!(target: "trie::hash_builder", "0 or 1 state masks means we have no more elements to process"); + return; + } + + current.truncate(preceding_len); + trace!(target: "trie::hash_builder", ?current, "truncated nibbles to {} bytes", preceding_len); + + trace!(target: "trie::hash_builder", groups = ?self.state_masks, "popping empty state masks"); + while self.state_masks.last() == Some(&TrieMask::default()) { + self.state_masks.pop(); + } + + build_extensions = true; + + i += 1; + } + } + + /// Given the size of the longest common prefix, it proceeds to create a branch node + /// from the state mask and existing stack state, and store its RLP to the top of the stack, + /// after popping all the relevant elements from the stack. + /// + /// Returns the hashes of the children of the branch node, only if `updated_branch_nodes` is + /// enabled. + fn push_branch_node(&mut self, _current: &Nibbles, len: usize) -> Vec<B256> { + let state_mask = self.state_masks[len]; + let hash_mask = self.hash_masks[len]; + let branch_node = BranchNodeRef::new(&self.stack, state_mask); + // Avoid calculating this value if it's not needed. + let children = if self.updated_branch_nodes.is_some() { + branch_node.child_hashes(hash_mask).collect() + } else { + vec![] + }; + + let branch_hash = branch_node.hash(); + + // TODO: enable proof retention + // self.retain_proof_from_stack(&current.slice(..len)); + + // Clears the stack from the branch node elements + let first_child_idx = branch_node.first_child_index(); + trace!( + target: "trie::hash_builder", + new_len = first_child_idx, + old_len = self.stack.len(), + "resizing stack to prepare branch node" + ); + self.stack.resize_with(first_child_idx, Default::default); + + trace!(target: "trie::hash_builder", ?branch_hash, "pushing branch node with {state_mask:?} mask + from stack"); + + self.stack.push(branch_hash); + children + } + + /// Given the current nibble prefix and the highest common prefix length, proceeds + /// to update the masks for the next level and store the branch node and the + /// masks in the database. We will use that when consuming the intermediate nodes + /// from the database to efficiently build the trie. + fn store_branch_node(&mut self, current: &Nibbles, len: usize, children: Vec<B256>) { + trace!(target: "trie::hash_builder", ?current, ?len, ?children, "store branch node"); + if len > 0 { + let parent_index = len - 1; + self.hash_masks[parent_index] |= TrieMask::from_nibble(current[parent_index]); + } + + let store_in_db_trie = !self.tree_masks[len].is_empty() || !self.hash_masks[len].is_empty(); + if store_in_db_trie { + if len > 0 { + let parent_index = len - 1; + self.tree_masks[parent_index] |= TrieMask::from_nibble(current[parent_index]); + } + + if self.updated_branch_nodes.is_some() { + let common_prefix = current.slice(..len); + let node = BranchNodeCompact::new( + self.state_masks[len] & CHILD_INDEX_MASK, + self.tree_masks[len], + self.hash_masks[len], + children, + (len == 0).then(|| self.current_root()), + ); + trace!(target: "trie::hash_builder", ?node, "storing updated intermediate node"); + self.updated_branch_nodes + .as_mut() + .expect("updates_branch_nodes is some") + .insert(common_prefix, node); + } + } + } + + // TODO(scroll): Enable proof retention + // fn retain_proof_from_stack(&mut self, prefix: &Nibbles) { + // if let Some(proof_retainer) = self.proof_retainer.as_mut() { + // proof_retainer.retain( + // prefix, + // self.stack.last().expect("there should be at least one stack item").as_ref(), + // ); + // } + // } + + fn update_masks(&mut self, current: &Nibbles, len_from: usize) { + if len_from > 0 { + let flag = TrieMask::from_nibble(current[len_from - 1]); + + self.hash_masks[len_from - 1] &= !flag; + + if !self.tree_masks[current.len() - 1].is_empty() { + self.tree_masks[len_from - 1] |= flag; + } + } + } + + fn resize_masks(&mut self, new_len: usize) { + trace!( + target: "trie::hash_builder", + new_len, + old_tree_mask_len = self.tree_masks.len(), + old_hash_mask_len = self.hash_masks.len(), + "resizing tree/hash masks" + ); + self.tree_masks.resize(new_len, TrieMask::default()); + self.hash_masks.resize(new_len, TrieMask::default()); + } +} + +// TODO(scroll): Introduce generic for the HashBuilder. +impl From<reth_trie::HashBuilder> for HashBuilder { + fn from(hash_builder: reth_trie::HashBuilder) -> Self { + Self { + key: hash_builder.key, + value: hash_builder.value, + stack: hash_builder + .stack + .into_iter() + .map(|x| x.as_slice().try_into().expect("RlpNode contains 32 byte hashes")) + .collect(), + state_masks: hash_builder.groups, + tree_masks: hash_builder.tree_masks, + hash_masks: hash_builder.hash_masks, + stored_in_database: hash_builder.stored_in_database, + updated_branch_nodes: hash_builder.updated_branch_nodes, + proof_retainer: hash_builder.proof_retainer, + } + } +} + +impl From<HashBuilder> for reth_trie::HashBuilder { + fn from(value: HashBuilder) -> Self { + Self { + key: value.key, + value: value.value, + stack: value + .stack + .into_iter() + .map(|x| { + reth_trie::RlpNode::from_raw(&x.0).expect("32 byte hash can be cast to RlpNode") + }) + .collect(), + groups: value.state_masks, + tree_masks: value.tree_masks, + hash_masks: value.hash_masks, + stored_in_database: value.stored_in_database, + updated_branch_nodes: value.updated_branch_nodes, + proof_retainer: value.proof_retainer, + rlp_buf: Default::default(), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::key::AsBytes; + use alloc::collections::BTreeMap; + use poseidon_bn254::{hash_with_domain, Fr, PrimeField}; + + #[test] + fn test_basic_trie() { + // Test a basic trie consisting of three key value pairs: + // (0, 0, 0, 0, ... , 0) + // (0, 0, 0, 1, ... , 0) + // (0, 0, 1, 0, ... , 0) + // (1, 1, 1, 0, ... , 0) + // (1, 1, 1, 1, ... , 0) + // The branch associated with key 0xF will be collapsed into a single leaf. + + let leaf_1_key = Nibbles::from_nibbles_unchecked([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + let leaf_2_key = Nibbles::from_nibbles_unchecked([ + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + let leaf_3_key = Nibbles::from_nibbles_unchecked([ + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + let leaf_4_key = Nibbles::from_nibbles_unchecked([ + 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + let leaf_5_key = Nibbles::from_nibbles_unchecked([ + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + let leaf_keys = [ + leaf_1_key.clone(), + leaf_2_key.clone(), + leaf_3_key.clone(), + leaf_4_key.clone(), + leaf_5_key.clone(), + ]; + + let leaf_values = leaf_keys + .into_iter() + .enumerate() + .map(|(i, key)| { + let mut leaf_value = [0u8; 32]; + leaf_value[0] = i as u8 + 1; + (key, leaf_value) + }) + .collect::<BTreeMap<_, _>>(); + + let leaf_hashes: BTreeMap<_, _> = leaf_values + .iter() + .map(|(key, value)| { + let key_fr = + Fr::from_repr_vartime(key.as_bytes()).expect("key is valid field element"); + let value = Fr::from_repr_vartime(*value).expect("value is a valid field element"); + let hash = hash_with_domain(&[key_fr, value], crate::LEAF_NODE_DOMAIN); + (key.clone(), hash) + }) + .collect(); + + let mut hb = HashBuilder::default().with_updates(true); + + for (key, val) in &leaf_values { + hb.add_leaf(key.clone(), val); + } + + let root = hb.root(); + + // node_000 -> hash(leaf_1, leaf_2) LTRT + // node_00 -> hash(node_000, leaf_3) LBRT + // node_0 -> hash(node_00, EMPTY) LBRT + // node_111 -> hash(leaf_4, leaf_5) LTRT + // node_11 -> hash(EMPTY, node_111) LTRB + // node_1 -> hash(EMPTY, node_11) LTRB + // root -> hash(node_0, node_1) LBRB + + let expected: B256 = { + let node_000 = hash_with_domain( + &[*leaf_hashes.get(&leaf_1_key).unwrap(), *leaf_hashes.get(&leaf_2_key).unwrap()], + crate::BRANCH_NODE_LTRT_DOMAIN, + ); + let node_00 = hash_with_domain( + &[node_000, *leaf_hashes.get(&leaf_3_key).unwrap()], + crate::BRANCH_NODE_LBRT_DOMAIN, + ); + let node_0 = hash_with_domain(&[node_00, Fr::zero()], crate::BRANCH_NODE_LBRT_DOMAIN); + let node_111 = hash_with_domain( + &[*leaf_hashes.get(&leaf_4_key).unwrap(), *leaf_hashes.get(&leaf_5_key).unwrap()], + crate::BRANCH_NODE_LTRT_DOMAIN, + ); + let node_11 = hash_with_domain(&[Fr::zero(), node_111], crate::BRANCH_NODE_LTRB_DOMAIN); + let node_1 = hash_with_domain(&[Fr::zero(), node_11], crate::BRANCH_NODE_LTRB_DOMAIN); + + let mut root = + hash_with_domain(&[node_0, node_1], crate::BRANCH_NODE_LBRB_DOMAIN).to_repr(); + root.reverse(); + root.into() + }; + + assert_eq!(expected, root); + } +}
diff --git reth/crates/scroll/trie/src/key.rs scroll-reth/crates/scroll/trie/src/key.rs new file mode 100644 index 0000000000000000000000000000000000000000..0a58bd9f9ce06ceb31532ec32ace2c10471b079f --- /dev/null +++ scroll-reth/crates/scroll/trie/src/key.rs @@ -0,0 +1,21 @@ +use reth_trie::Nibbles; + +/// A type that can return its bytes representation encoded as a little-endian on 32 bytes. +pub(crate) trait AsBytes { + /// Returns the type as its canonical little-endian representation on 32 bytes. + fn as_bytes(&self) -> [u8; 32]; +} + +impl AsBytes for Nibbles { + fn as_bytes(&self) -> [u8; 32] { + // This is strange we are now representing the leaf key using big endian?? + let mut result = [0u8; 32]; + for (byte_index, bytes) in self.as_slice().chunks(8).enumerate() { + for (bit_index, byte) in bytes.iter().enumerate() { + result[byte_index] |= byte << bit_index; + } + } + + result + } +}
diff --git reth/crates/scroll/trie/src/leaf.rs scroll-reth/crates/scroll/trie/src/leaf.rs new file mode 100644 index 0000000000000000000000000000000000000000..79216d8f562dfdf8e2bbe231e0454858e0459ae2 --- /dev/null +++ scroll-reth/crates/scroll/trie/src/leaf.rs @@ -0,0 +1,23 @@ +use super::LEAF_NODE_DOMAIN; +use crate::key::AsBytes; +use alloy_primitives::B256; +use alloy_trie::nodes::LeafNodeRef; +use poseidon_bn254::{hash_with_domain, Fr, PrimeField}; + +/// A trait used to hash the leaf node. +pub(crate) trait HashLeaf { + /// Hash the leaf node. + fn hash_leaf(&self) -> B256; +} + +impl HashLeaf for LeafNodeRef<'_> { + fn hash_leaf(&self) -> B256 { + let leaf_key = + Fr::from_repr_vartime(self.key.as_bytes()).expect("leaf key is a valid field element"); + let leaf_value = Fr::from_repr_vartime( + <[u8; 32]>::try_from(self.value).expect("leaf value is 32 bytes"), + ) + .expect("leaf value is a valid field element"); + hash_with_domain(&[leaf_key, leaf_value], LEAF_NODE_DOMAIN).to_repr().into() + } +}
diff --git reth/crates/scroll/trie/src/lib.rs scroll-reth/crates/scroll/trie/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..5b1a5fd5aa93d6ad8bc735ff4a6d4bb1fe17679e --- /dev/null +++ scroll-reth/crates/scroll/trie/src/lib.rs @@ -0,0 +1,37 @@ +//! Fast binary Merkle-Patricia Trie (zktrie) state root calculator and proof generator for +//! prefix-sorted bits. + +#![cfg_attr(not(doctest), doc = include_str!("../assets/zktrie.md"))] + +#[macro_use] +#[allow(unused_imports)] +extern crate alloc; + +mod branch; + +mod constants; +pub use constants::EMPTY_ROOT_HASH; + +mod key; +mod leaf; +mod sub_tree; + +mod hash_builder; +pub use hash_builder::HashBuilder; + +use poseidon_bn254::Fr; + +/// The hashing domain for leaf nodes. +pub const LEAF_NODE_DOMAIN: Fr = Fr::from_raw([4, 0, 0, 0]); + +/// The hashing domain for a branch node with two terminal children. +pub const BRANCH_NODE_LTRT_DOMAIN: Fr = Fr::from_raw([6, 0, 0, 0]); + +/// The hashing domain for a branch node with a left terminal child and a right branch child. +pub const BRANCH_NODE_LTRB_DOMAIN: Fr = Fr::from_raw([7, 0, 0, 0]); + +/// The hashing domain for a branch node with a left branch child and a right terminal child. +pub const BRANCH_NODE_LBRT_DOMAIN: Fr = Fr::from_raw([8, 0, 0, 0]); + +/// The hashing domain for a branch node with two branch children. +pub const BRANCH_NODE_LBRB_DOMAIN: Fr = Fr::from_raw([9, 0, 0, 0]);
diff --git reth/crates/scroll/trie/src/sub_tree.rs scroll-reth/crates/scroll/trie/src/sub_tree.rs new file mode 100644 index 0000000000000000000000000000000000000000..31edad05c4166dcab958f28450206e81b61ab13f --- /dev/null +++ scroll-reth/crates/scroll/trie/src/sub_tree.rs @@ -0,0 +1,44 @@ +use super::{BRANCH_NODE_LBRT_DOMAIN, BRANCH_NODE_LTRB_DOMAIN}; +use alloy_primitives::{hex, B256}; +use alloy_trie::Nibbles; +use core::fmt; +use poseidon_bn254::{hash_with_domain, Fr, PrimeField}; + +/// [`SubTreeRef`] is a structure that allows for calculation of the root of a sparse binary Merkle +/// tree consisting of a single leaf node. +pub(crate) struct SubTreeRef<'a> { + /// The key to the child node. + pub key: &'a Nibbles, + /// A pointer to the child node. + pub child: &'a B256, +} + +impl<'a> SubTreeRef<'a> { + /// Creates a new subtree with the given key and a pointer to the child. + #[inline] + pub(crate) const fn new(key: &'a Nibbles, child: &'a B256) -> Self { + Self { key, child } + } + + pub(crate) fn root(&self) -> B256 { + let mut tree_root = + Fr::from_repr_vartime(self.child.0).expect("child is a valid field element"); + for bit in self.key.as_slice().iter().rev() { + tree_root = if *bit == 0 { + hash_with_domain(&[tree_root, Fr::zero()], BRANCH_NODE_LBRT_DOMAIN) + } else { + hash_with_domain(&[Fr::zero(), tree_root], BRANCH_NODE_LTRB_DOMAIN) + }; + } + tree_root.to_repr().into() + } +} + +impl fmt::Debug for SubTreeRef<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SubTreeRef") + .field("key", &self.key) + .field("node", &hex::encode(self.child)) + .finish() + } +}
diff --git reth/crates/scroll/txpool/Cargo.toml scroll-reth/crates/scroll/txpool/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..138e7ebb8bf46bd302d71586329e46240e4cac39 --- /dev/null +++ scroll-reth/crates/scroll/txpool/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "reth-scroll-txpool" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# ethereum +alloy-consensus.workspace = true +alloy-eips.workspace = true +alloy-primitives.workspace = true + +# reth +reth-chainspec.workspace = true +reth-primitives-traits.workspace = true +reth-revm.workspace = true +reth-storage-api.workspace = true +reth-transaction-pool.workspace = true + +# revm-scroll +revm-scroll.workspace = true + +# reth-scroll +reth-scroll-consensus.workspace = true +reth-scroll-evm.workspace = true +reth-scroll-forks.workspace = true +reth-scroll-primitives.workspace = true + +# scroll-alloy +scroll-alloy-consensus.workspace = true + +# misc +c-kzg.workspace = true +derive_more.workspace = true +parking_lot.workspace = true + +[dev-dependencies] +reth-scroll-chainspec.workspace = true +reth-provider = { workspace = true, features = ["test-utils"] }
diff --git reth/crates/scroll/txpool/src/lib.rs scroll-reth/crates/scroll/txpool/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..a1885e05acb3cd8a06c49e0daa63bdcba9ff2181 --- /dev/null +++ scroll-reth/crates/scroll/txpool/src/lib.rs @@ -0,0 +1,16 @@ +//! Transaction pool for Scroll node. + +mod transaction; +pub use transaction::ScrollPooledTransaction; + +mod validator; +pub use validator::{ScrollL1BlockInfo, ScrollTransactionValidator}; + +use reth_transaction_pool::{CoinbaseTipOrdering, Pool, TransactionValidationTaskExecutor}; + +/// Type alias for default scroll transaction pool +pub type ScrollTransactionPool<Client, S, T = ScrollPooledTransaction> = Pool< + TransactionValidationTaskExecutor<ScrollTransactionValidator<Client, T>>, + CoinbaseTipOrdering<T>, + S, +>;
diff --git reth/crates/scroll/txpool/src/transaction.rs scroll-reth/crates/scroll/txpool/src/transaction.rs new file mode 100644 index 0000000000000000000000000000000000000000..e0fdf7b66c1671086538b75c36397ba988136177 --- /dev/null +++ scroll-reth/crates/scroll/txpool/src/transaction.rs @@ -0,0 +1,263 @@ +use alloy_consensus::{transaction::Recovered, BlobTransactionValidationError, Typed2718}; +use alloy_eips::{ + eip2930::AccessList, eip7594::BlobTransactionSidecarVariant, eip7702::SignedAuthorization, +}; +use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, U256}; +use c_kzg::KzgSettings; +use core::fmt::Debug; +use reth_primitives_traits::{InMemorySize, SignedTransaction}; +use reth_scroll_primitives::ScrollTransactionSigned; +use reth_transaction_pool::{ + EthBlobTransactionSidecar, EthPoolTransaction, EthPooledTransaction, PoolTransaction, +}; +use scroll_alloy_consensus::ScrollTransaction; +use std::sync::{Arc, OnceLock}; + +/// Pool transaction for Scroll. +/// +/// This type wraps the actual transaction and caches values that are frequently used by the pool. +/// For payload building this lazily tracks values that are required during payload building: +/// - Estimated compressed size of this transaction +#[derive(Debug, Clone, derive_more::Deref)] +pub struct ScrollPooledTransaction< + Cons = ScrollTransactionSigned, + Pooled = scroll_alloy_consensus::ScrollPooledTransaction, +> { + #[deref] + inner: EthPooledTransaction<Cons>, + /// The pooled transaction type. + _pd: core::marker::PhantomData<Pooled>, + + /// Cached EIP-2718 encoded bytes of the transaction, lazily computed. + encoded_2718: OnceLock<Bytes>, +} + +impl<Cons: SignedTransaction, Pooled> ScrollPooledTransaction<Cons, Pooled> { + /// Create new instance of [Self]. + pub fn new(transaction: Recovered<Cons>, encoded_length: usize) -> Self { + Self { + inner: EthPooledTransaction::new(transaction, encoded_length), + _pd: core::marker::PhantomData, + encoded_2718: Default::default(), + } + } + + /// Returns lazily computed EIP-2718 encoded bytes of the transaction. + pub fn encoded_2718(&self) -> &Bytes { + self.encoded_2718.get_or_init(|| self.inner.transaction().encoded_2718().into()) + } +} + +impl<Cons, Pooled> PoolTransaction for ScrollPooledTransaction<Cons, Pooled> +where + Cons: SignedTransaction + From<Pooled>, + Pooled: SignedTransaction + TryFrom<Cons, Error: core::error::Error>, +{ + type TryFromConsensusError = <Pooled as TryFrom<Cons>>::Error; + type Consensus = Cons; + type Pooled = Pooled; + + fn clone_into_consensus(&self) -> Recovered<Self::Consensus> { + self.inner.transaction().clone() + } + + fn into_consensus(self) -> Recovered<Self::Consensus> { + self.inner.transaction + } + + fn from_pooled(tx: Recovered<Self::Pooled>) -> Self { + let encoded_len = tx.encode_2718_len(); + Self::new(tx.convert(), encoded_len) + } + + fn hash(&self) -> &TxHash { + self.inner.transaction.tx_hash() + } + + fn sender(&self) -> Address { + self.inner.transaction.signer() + } + + fn sender_ref(&self) -> &Address { + self.inner.transaction.signer_ref() + } + + fn cost(&self) -> &U256 { + &self.inner.cost + } + + fn encoded_length(&self) -> usize { + self.inner.encoded_length + } +} + +impl<Cons: Typed2718, Pooled> Typed2718 for ScrollPooledTransaction<Cons, Pooled> { + fn ty(&self) -> u8 { + self.inner.ty() + } +} + +impl<Cons: InMemorySize, Pooled> InMemorySize for ScrollPooledTransaction<Cons, Pooled> { + fn size(&self) -> usize { + self.inner.size() + } +} + +impl<Cons, Pooled> alloy_consensus::Transaction for ScrollPooledTransaction<Cons, Pooled> +where + Cons: alloy_consensus::Transaction, + Pooled: Debug + Send + Sync + 'static, +{ + fn chain_id(&self) -> Option<u64> { + self.inner.chain_id() + } + + fn nonce(&self) -> u64 { + self.inner.nonce() + } + + fn gas_limit(&self) -> u64 { + self.inner.gas_limit() + } + + fn gas_price(&self) -> Option<u128> { + self.inner.gas_price() + } + + fn max_fee_per_gas(&self) -> u128 { + self.inner.max_fee_per_gas() + } + + fn max_priority_fee_per_gas(&self) -> Option<u128> { + self.inner.max_priority_fee_per_gas() + } + + fn max_fee_per_blob_gas(&self) -> Option<u128> { + self.inner.max_fee_per_blob_gas() + } + + fn priority_fee_or_price(&self) -> u128 { + self.inner.priority_fee_or_price() + } + + fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 { + self.inner.effective_gas_price(base_fee) + } + + fn is_dynamic_fee(&self) -> bool { + self.inner.is_dynamic_fee() + } + + fn kind(&self) -> TxKind { + self.inner.kind() + } + + fn is_create(&self) -> bool { + self.inner.is_create() + } + + fn value(&self) -> U256 { + self.inner.value() + } + + fn input(&self) -> &Bytes { + self.inner.input() + } + + fn access_list(&self) -> Option<&AccessList> { + self.inner.access_list() + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + self.inner.blob_versioned_hashes() + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + self.inner.authorization_list() + } +} + +impl<Cons, Pooled> EthPoolTransaction for ScrollPooledTransaction<Cons, Pooled> +where + Cons: SignedTransaction + From<Pooled>, + Pooled: SignedTransaction + TryFrom<Cons>, + <Pooled as TryFrom<Cons>>::Error: core::error::Error, +{ + fn take_blob(&mut self) -> EthBlobTransactionSidecar { + EthBlobTransactionSidecar::None + } + + fn try_into_pooled_eip4844( + self, + _sidecar: Arc<BlobTransactionSidecarVariant>, + ) -> Option<Recovered<Self::Pooled>> { + None + } + + fn try_from_eip4844( + _tx: Recovered<Self::Consensus>, + _sidecar: BlobTransactionSidecarVariant, + ) -> Option<Self> { + None + } + + fn validate_blob( + &self, + _sidecar: &BlobTransactionSidecarVariant, + _settings: &KzgSettings, + ) -> Result<(), BlobTransactionValidationError> { + Err(BlobTransactionValidationError::NotBlobTransaction(self.ty())) + } +} + +impl<Cons: ScrollTransaction, Pooled> ScrollTransaction for ScrollPooledTransaction<Cons, Pooled> { + fn is_l1_message(&self) -> bool { + self.transaction.is_l1_message() + } + + fn queue_index(&self) -> Option<u64> { + self.transaction.queue_index() + } +} + +#[cfg(test)] +mod tests { + use crate::{ScrollPooledTransaction, ScrollTransactionValidator}; + use alloy_consensus::{transaction::Recovered, Signed}; + use alloy_eips::eip2718::Encodable2718; + use alloy_primitives::Signature; + use reth_provider::test_utils::MockEthProvider; + use reth_scroll_chainspec::SCROLL_MAINNET; + use reth_scroll_primitives::ScrollTransactionSigned; + use reth_transaction_pool::{ + blobstore::InMemoryBlobStore, validate::EthTransactionValidatorBuilder, TransactionOrigin, + TransactionValidationOutcome, + }; + use scroll_alloy_consensus::{ScrollTypedTransaction, TxL1Message}; + #[test] + fn validate_scroll_transaction() { + let client = MockEthProvider::default().with_chain_spec(SCROLL_MAINNET.clone()); + let validator = EthTransactionValidatorBuilder::new(client) + .no_shanghai() + .no_cancun() + .build(InMemoryBlobStore::default()); + let validator = ScrollTransactionValidator::new(validator); + + let origin = TransactionOrigin::External; + let signer = Default::default(); + let deposit_tx = ScrollTypedTransaction::L1Message(TxL1Message::default()); + let signature = Signature::test_signature(); + let signed_tx: ScrollTransactionSigned = Signed::new_unhashed(deposit_tx, signature).into(); + let signed_recovered = Recovered::new_unchecked(signed_tx, signer); + let len = signed_recovered.encode_2718_len(); + let pooled_tx: ScrollPooledTransaction = + ScrollPooledTransaction::new(signed_recovered, len); + let outcome = validator.validate_one(origin, pooled_tx); + + let err = match outcome { + TransactionValidationOutcome::Invalid(_, err) => err, + _ => panic!("Expected invalid transaction"), + }; + assert_eq!(err.to_string(), "transaction type not supported"); + } +}
diff --git reth/crates/scroll/txpool/src/validator.rs scroll-reth/crates/scroll/txpool/src/validator.rs new file mode 100644 index 0000000000000000000000000000000000000000..24a4d76dedee0817eabbd902472164ce1cd8c742 --- /dev/null +++ scroll-reth/crates/scroll/txpool/src/validator.rs @@ -0,0 +1,268 @@ +use alloy_consensus::BlockHeader; +use alloy_eips::Encodable2718; +use parking_lot::RwLock; +use reth_chainspec::ChainSpecProvider; +use reth_primitives_traits::{ + transaction::error::InvalidTransactionError, Block, GotExpected, SealedBlock, +}; +use reth_revm::database::StateProviderDatabase; +use reth_scroll_consensus::MAX_ROLLUP_FEE; +use reth_scroll_evm::{ + compute_compression_ratio, spec_id_at_timestamp_and_number, RethL1BlockInfo, +}; +use reth_scroll_forks::ScrollHardforks; +use reth_storage_api::{BlockReaderIdExt, StateProviderFactory}; +use reth_transaction_pool::{ + EthPoolTransaction, EthTransactionValidator, TransactionOrigin, TransactionValidationOutcome, + TransactionValidator, +}; +use revm_scroll::l1block::L1BlockInfo; +use scroll_alloy_consensus::ScrollTransaction; +use std::sync::{ + atomic::{AtomicU64, Ordering}, + Arc, +}; + +/// Tracks additional infos for the current block. +#[derive(Debug, Default)] +pub struct ScrollL1BlockInfo { + /// The current L1 block info. + l1_block_info: RwLock<L1BlockInfo>, + /// Current block timestamp. + timestamp: AtomicU64, + /// Current block number. + number: AtomicU64, +} + +/// Validator for Scroll transactions. +#[derive(Debug, Clone)] +pub struct ScrollTransactionValidator<Client, Tx> { + /// The type that performs the actual validation. + inner: EthTransactionValidator<Client, Tx>, + /// Additional block info required for validation. + block_info: Arc<ScrollL1BlockInfo>, + /// If true, ensure that the transaction's sender has enough balance to cover the L1 gas fee + /// derived from the tracked L1 block info. + require_l1_data_gas_fee: bool, +} + +impl<Client, Tx> ScrollTransactionValidator<Client, Tx> { + /// Returns the configured chain spec + pub fn chain_spec(&self) -> Arc<Client::ChainSpec> + where + Client: ChainSpecProvider, + { + self.inner.chain_spec() + } + + /// Returns the configured client + pub fn client(&self) -> &Client { + self.inner.client() + } + + /// Returns the current block timestamp. + fn block_timestamp(&self) -> u64 { + self.block_info.timestamp.load(Ordering::Relaxed) + } + + /// Returns the current block number. + fn block_number(&self) -> u64 { + self.block_info.number.load(Ordering::Relaxed) + } + + /// Whether to ensure that the transaction's sender has enough balance to also cover the L1 gas + /// fee. + pub fn require_l1_data_gas_fee(self, require_l1_data_gas_fee: bool) -> Self { + Self { require_l1_data_gas_fee, ..self } + } + + /// Returns whether this validator also requires the transaction's sender to have enough balance + /// to cover the L1 gas fee. + pub const fn requires_l1_data_gas_fee(&self) -> bool { + self.require_l1_data_gas_fee + } +} + +impl<Client, Tx> ScrollTransactionValidator<Client, Tx> +where + Client: ChainSpecProvider<ChainSpec: ScrollHardforks> + StateProviderFactory + BlockReaderIdExt, + Tx: EthPoolTransaction + ScrollTransaction, +{ + /// Create a new [`ScrollTransactionValidator`]. + pub fn new(inner: EthTransactionValidator<Client, Tx>) -> Self { + let this = Self::with_block_info(inner, ScrollL1BlockInfo::default()); + if let Ok(Some(block)) = + this.inner.client().block_by_number_or_tag(alloy_eips::BlockNumberOrTag::Latest) + { + this.block_info.timestamp.store(block.header().timestamp(), Ordering::Relaxed); + this.block_info.number.store(block.header().number(), Ordering::Relaxed); + this.update_l1_block_info(block.header()); + } + + this + } + + /// Create a new [`ScrollTransactionValidator`] with the given [`ScrollL1BlockInfo`]. + pub fn with_block_info( + inner: EthTransactionValidator<Client, Tx>, + block_info: ScrollL1BlockInfo, + ) -> Self { + Self { inner, block_info: Arc::new(block_info), require_l1_data_gas_fee: true } + } + + /// Update the L1 block info for the given header and system transaction, if any. + pub fn update_l1_block_info<H>(&self, header: &H) + where + H: BlockHeader, + { + self.block_info.timestamp.store(header.timestamp(), Ordering::Relaxed); + self.block_info.number.store(header.number(), Ordering::Relaxed); + + let provider = + self.client().state_by_block_number_or_tag(header.number().into()).expect("msg"); + let mut db = StateProviderDatabase::new(provider); + let spec_id = + spec_id_at_timestamp_and_number(header.timestamp(), header.number(), self.chain_spec()); + if let Ok(l1_block_info) = L1BlockInfo::try_fetch(&mut db, spec_id) { + *self.block_info.l1_block_info.write() = l1_block_info; + } + } + + /// Validates a single transaction. + /// + /// See also [`TransactionValidator::validate_transaction`] + /// + /// This behaves the same as [`EthTransactionValidator::validate_one`], but in addition, ensures + /// that the account has enough balance to cover the L1 gas cost. + pub fn validate_one( + &self, + origin: TransactionOrigin, + transaction: Tx, + ) -> TransactionValidationOutcome<Tx> { + if transaction.is_eip4844() { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::Eip4844Disabled.into(), + ) + } + if transaction.is_l1_message() { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::TxTypeNotSupported.into(), + ) + } + + let outcome = self.inner.validate_one(origin, transaction); + + if !self.requires_l1_data_gas_fee() { + // no need to check L1 gas fee + return outcome + } + + // ensure that the account has enough balance to cover the L1 gas cost + if let TransactionValidationOutcome::Valid { + balance, + state_nonce, + transaction: valid_tx, + propagate, + bytecode_hash, + authorities, + } = outcome + { + let mut l1_block_info = self.block_info.l1_block_info.read().clone(); + + let mut encoded = Vec::with_capacity(valid_tx.transaction().encoded_length()); + let tx = valid_tx.transaction().clone_into_consensus(); + tx.encode_2718(&mut encoded); + let compression_ratio = compute_compression_ratio(valid_tx.transaction().input()); + + let cost_addition = match l1_block_info.l1_tx_data_fee( + self.chain_spec(), + self.block_timestamp(), + self.block_number(), + &encoded, + Some(compression_ratio), + false, + ) { + Ok(cost) => cost, + Err(err) => { + return TransactionValidationOutcome::Error(*valid_tx.hash(), Box::new(err)) + } + }; + // Check rollup fee is under u64::MAX. + if cost_addition >= MAX_ROLLUP_FEE { + return TransactionValidationOutcome::Invalid( + valid_tx.into_transaction(), + InvalidTransactionError::GasUintOverflow.into(), + ) + } + + let cost = valid_tx.transaction().cost().saturating_add(cost_addition); + + // Checks for max cost + if cost > balance { + return TransactionValidationOutcome::Invalid( + valid_tx.into_transaction(), + InvalidTransactionError::InsufficientFunds( + GotExpected { got: balance, expected: cost }.into(), + ) + .into(), + ) + } + + return TransactionValidationOutcome::Valid { + balance, + state_nonce, + bytecode_hash, + transaction: valid_tx, + propagate, + authorities, + } + } + + outcome + } + + /// Validates all given transactions. + /// + /// Returns all outcomes for the given transactions in the same order. + /// + /// See also [`Self::validate_one`] + pub fn validate_all( + &self, + transactions: Vec<(TransactionOrigin, Tx)>, + ) -> Vec<TransactionValidationOutcome<Tx>> { + transactions.into_iter().map(|(origin, tx)| self.validate_one(origin, tx)).collect() + } +} + +impl<Client, Tx> TransactionValidator for ScrollTransactionValidator<Client, Tx> +where + Client: ChainSpecProvider<ChainSpec: ScrollHardforks> + StateProviderFactory + BlockReaderIdExt, + Tx: EthPoolTransaction + ScrollTransaction, +{ + type Transaction = Tx; + + async fn validate_transaction( + &self, + origin: TransactionOrigin, + transaction: Self::Transaction, + ) -> TransactionValidationOutcome<Self::Transaction> { + self.validate_one(origin, transaction) + } + + async fn validate_transactions( + &self, + transactions: Vec<(TransactionOrigin, Self::Transaction)>, + ) -> Vec<TransactionValidationOutcome<Self::Transaction>> { + self.validate_all(transactions) + } + + fn on_new_head_block<B>(&self, new_tip_block: &SealedBlock<B>) + where + B: Block, + { + self.inner.on_new_head_block(new_tip_block); + self.update_l1_block_info(new_tip_block.header()); + } +}
diff --git reth/crates/ethereum/cli/src/interface.rs scroll-reth/crates/ethereum/cli/src/interface.rs index ee4fbdfd60f55fe837324b53a1cb2a3f94460678..b41c61de7cbb55189dd391e2a7103870d599fc0b 100644 --- reth/crates/ethereum/cli/src/interface.rs +++ scroll-reth/crates/ethereum/cli/src/interface.rs @@ -326,7 +326,15 @@ use reth_node_core::args::ColorMode;   #[test] fn parse_color_mode() { - let reth = Cli::try_parse_args_from(["reth", "node", "--color", "always"]).unwrap(); + let reth = Cli::try_parse_args_from([ + "reth", + "node", + "--color", + "always", + "--builder.gaslimit", + "10000000", + ]) + .unwrap(); assert_eq!(reth.logs.color, ColorMode::Always); }   @@ -353,7 +361,8 @@ /// Tests that the log directory is parsed correctly when using the node command. It's /// always tied to the specific chain's name. #[test] fn parse_logs_path_node() { - let mut reth = Cli::try_parse_args_from(["reth", "node"]).unwrap(); + let mut reth = + Cli::try_parse_args_from(["reth", "node", "--builder.gaslimit", "10000000"]).unwrap(); if let Some(chain_spec) = reth.command.chain_spec() { reth.logs.log_file_directory = reth.logs.log_file_directory.join(chain_spec.chain.to_string()); @@ -365,7 +374,15 @@ let mut iter = SUPPORTED_CHAINS.iter(); iter.next(); for chain in iter { - let mut reth = Cli::try_parse_args_from(["reth", "node", "--chain", chain]).unwrap(); + let mut reth = Cli::try_parse_args_from([ + "reth", + "node", + "--chain", + chain, + "--builder.gaslimit", + "10000000", + ]) + .unwrap(); let chain = reth.command.chain_spec().map(|c| c.chain.to_string()).unwrap_or(String::new()); reth.logs.log_file_directory = reth.logs.log_file_directory.join(chain.clone());
diff --git reth/crates/ethereum/consensus/src/lib.rs scroll-reth/crates/ethereum/consensus/src/lib.rs index 621a69f88b7f9a0ced627de455956298544f2424..85d049e65b998a8a636aa337d1325be2212c7e14 100644 --- reth/crates/ethereum/consensus/src/lib.rs +++ scroll-reth/crates/ethereum/consensus/src/lib.rs @@ -29,7 +29,7 @@ Block, BlockHeader, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader, };   mod validation; -pub use validation::validate_block_post_execution; +pub use validation::{validate_block_post_execution, verify_receipts};   /// Ethereum beacon consensus ///
diff --git reth/crates/ethereum/consensus/src/validation.rs scroll-reth/crates/ethereum/consensus/src/validation.rs index 485828e6080849349ec0771d3bfcfd7ed2b0334c..01b0068db10b8260cca86b6a04f233ddb99aecba 100644 --- reth/crates/ethereum/consensus/src/validation.rs +++ scroll-reth/crates/ethereum/consensus/src/validation.rs @@ -68,7 +68,7 @@ }   /// Calculate the receipts root, and compare it against the expected receipts root and logs /// bloom. -fn verify_receipts<R: Receipt>( +pub fn verify_receipts<R: Receipt>( expected_receipts_root: B256, expected_logs_bloom: Bloom, receipts: &[R],
diff --git reth/crates/ethereum/hardforks/src/display.rs scroll-reth/crates/ethereum/hardforks/src/display.rs index e40a117d26a3bb990c9625c3adaf56a2f4c344c0..7eda386a3cc26e3efa55a60d5fc7f2aaadc31224 100644 --- reth/crates/ethereum/hardforks/src/display.rs +++ scroll-reth/crates/ethereum/hardforks/src/display.rs @@ -119,7 +119,7 @@ format( "Pre-merge hard forks (block based)", &self.pre_merge, - self.with_merge.is_empty(), + self.with_merge.is_empty() && self.post_merge.is_empty(), f, )?;
diff --git reth/crates/ethereum/node/tests/e2e/dev.rs scroll-reth/crates/ethereum/node/tests/e2e/dev.rs index ad214b04fe06434d0c84f3735442f283b3a94734..3b14c2401024eacaed10f3f2ce2cdb87dbaca2be 100644 --- reth/crates/ethereum/node/tests/e2e/dev.rs +++ scroll-reth/crates/ethereum/node/tests/e2e/dev.rs @@ -3,10 +3,10 @@ use alloy_genesis::Genesis; use alloy_primitives::{b256, hex}; use futures::StreamExt; use reth_chainspec::ChainSpec; -use reth_node_api::{BlockBody, FullNodeComponents, FullNodePrimitives, NodeTypes}; +use reth_node_api::{BlockBody, FullNodeComponents, FullNodePrimitives, NodeAddOns, NodeTypes}; use reth_node_builder::{ - rpc::RethRpcAddOns, DebugNodeLauncher, EngineNodeLauncher, FullNode, NodeBuilder, NodeConfig, - NodeHandle, + rpc::{RethRpcAddOns, RpcHandleProvider}, + DebugNodeLauncher, EngineNodeLauncher, FullNode, NodeBuilder, NodeConfig, NodeHandle, }; use reth_node_core::args::DevArgs; use reth_node_ethereum::{node::EthereumAddOns, EthereumNode}; @@ -50,6 +50,7 @@ where N: FullNodeComponents<Provider: CanonStateSubscriptions>, AddOns: RethRpcAddOns<N, EthApi: EthTransactions>, N::Types: NodeTypes<Primitives: FullNodePrimitives>, + <AddOns as NodeAddOns<N>>::Handle: RpcHandleProvider<N, <AddOns as RethRpcAddOns<N>>::EthApi>, { let mut notifications = node.provider.canonical_state_stream();   @@ -58,7 +59,7 @@ let raw_tx = hex!( "02f876820a28808477359400847735940082520894ab0840c0e43688012c1adb0f5e3fc665188f83d28a029d394a5d630544000080c080a0a044076b7e67b5deecc63f61a8d7913fab86ca365b344b5759d1fe3563b4c39ea019eab979dd000da04dfc72bb0377c092d30fd9e1cab5ae487de49586cc8b0090" );   - let eth_api = node.rpc_registry.eth_api(); + let eth_api = node.rpc_handle().rpc_registry.eth_api();   let hash = eth_api.send_raw_transaction(raw_tx.into()).await.unwrap();
diff --git reth/crates/ethereum/node/tests/e2e/pool.rs scroll-reth/crates/ethereum/node/tests/e2e/pool.rs index 9187cb61405bf4e5453ff99c13cb613c9ff36d6c..027b59a7ae9479f0d33a87a7f5e1b4be927d8a46 100644 --- reth/crates/ethereum/node/tests/e2e/pool.rs +++ scroll-reth/crates/ethereum/node/tests/e2e/pool.rs @@ -45,7 +45,7 @@ .cancun_activated() .build(), ); let node_config = NodeConfig::test() - .with_chain(chain_spec) + .with_chain(chain_spec.clone()) .with_unused_ports() .with_rpc(RpcServerArgs::default().with_unused_ports().with_http()); let NodeHandle { node, node_exit_future: _ } = NodeBuilder::new(node_config.clone()) @@ -67,6 +67,7 @@ executor.spawn_critical( "txpool maintenance task", reth_transaction_pool::maintain::maintain_transaction_pool_future( node.inner.provider.clone(), + chain_spec, txpool.clone(), node.inner.provider.clone().canonical_state_stream(), executor.clone(), @@ -120,7 +121,7 @@ .build(), ); let genesis_hash = chain_spec.genesis_hash(); let node_config = NodeConfig::test() - .with_chain(chain_spec) + .with_chain(chain_spec.clone()) .with_unused_ports() .with_rpc(RpcServerArgs::default().with_unused_ports().with_http()); let NodeHandle { node, node_exit_future: _ } = NodeBuilder::new(node_config.clone()) @@ -139,6 +140,7 @@ executor.spawn_critical( "txpool maintenance task", reth_transaction_pool::maintain::maintain_transaction_pool_future( node.inner.provider.clone(), + chain_spec, txpool.clone(), node.inner.provider.clone().canonical_state_stream(), executor.clone(), @@ -269,6 +271,7 @@ executor.spawn_critical( "txpool maintenance task", reth_transaction_pool::maintain::maintain_transaction_pool_future( node.inner.provider.clone(), + MAINNET.clone(), txpool.clone(), node.inner.provider.clone().canonical_state_stream(), executor.clone(),
diff --git reth/crates/optimism/chainspec/src/lib.rs scroll-reth/crates/optimism/chainspec/src/lib.rs index 44361c7abf588b7258774cc47aea9c3f4f432054..33e3655df8d5919d7f1b1bcdfb87475ea3ccd136 100644 --- reth/crates/optimism/chainspec/src/lib.rs +++ scroll-reth/crates/optimism/chainspec/src/lib.rs @@ -66,7 +66,8 @@ use alloy_primitives::{B256, U256}; use derive_more::{Constructor, Deref, From, Into}; use reth_chainspec::{ BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder, DepositContract, - DisplayHardforks, EthChainSpec, EthereumHardforks, ForkFilter, ForkId, Hardforks, Head, + DisplayHardforks, EthChainSpec, EthereumCapabilities, EthereumHardforks, ForkFilter, ForkId, + Hardforks, Head, }; use reth_ethereum_forks::{ChainHardforks, EthereumHardfork, ForkCondition}; use reth_network_peers::NodeRecord; @@ -304,6 +305,8 @@ self.inner.next_block_base_fee(parent, target_timestamp) } } } + +impl EthereumCapabilities for OpChainSpec {}   impl Hardforks for OpChainSpec { fn fork<H: Hardfork>(&self, fork: H) -> ForkCondition {
diff --git reth/crates/optimism/cli/src/lib.rs scroll-reth/crates/optimism/cli/src/lib.rs index 4fca639fb280b993fa322ec4efb70adb567d9ad6..90669099f88df8e7530b4823a9c6124e7363bcd8 100644 --- reth/crates/optimism/cli/src/lib.rs +++ scroll-reth/crates/optimism/cli/src/lib.rs @@ -138,7 +138,12 @@ use reth_optimism_node::args::RollupArgs;   #[test] fn parse_dev() { - let cmd = NodeCommand::<OpChainSpecParser, NoArgs>::parse_from(["op-reth", "--dev"]); + let cmd = NodeCommand::<OpChainSpecParser, NoArgs>::parse_from([ + "op-reth", + "--dev", + "--builder.gaslimit", + "30000000", + ]); let chain = OP_DEV.clone(); assert_eq!(cmd.chain.chain, chain.chain); assert_eq!(cmd.chain.genesis_hash(), chain.genesis_hash()); @@ -189,6 +194,8 @@ "--metrics", "9003", "--log.file.max-size", "100", + "--builder.gaslimit", + "10000000", ]);   match cmd.command {
diff --git reth/crates/optimism/rpc/src/sequencer.rs scroll-reth/crates/optimism/rpc/src/sequencer.rs index c3b543638bb631afbe389f67876f487031ecdc01..2e66a30275f83abfe70310fc5307e5f06074b4d7 100644 --- reth/crates/optimism/rpc/src/sequencer.rs +++ scroll-reth/crates/optimism/rpc/src/sequencer.rs @@ -121,6 +121,7 @@ Ok(Self { inner: Arc::new(inner) }) }   /// Returns the network of the client + #[allow(clippy::missing_const_for_fn)] pub fn endpoint(&self) -> &str { &self.inner.sequencer_endpoint }
diff --git reth/crates/primitives-traits/Cargo.toml scroll-reth/crates/primitives-traits/Cargo.toml index a5bdd9a0ae7bd5008d0d23deca848ba3bd1f82d0..2bf898ca063df564681867fcb594bc9796154ca0 100644 --- reth/crates/primitives-traits/Cargo.toml +++ scroll-reth/crates/primitives-traits/Cargo.toml @@ -22,9 +22,14 @@ alloy-genesis.workspace = true alloy-primitives = { workspace = true, features = ["k256"] } alloy-rlp.workspace = true alloy-trie.workspace = true -revm-primitives.workspace = true + +# revm +revm-primitives = { workspace = true, default-features = false } revm-bytecode.workspace = true revm-state.workspace = true + +# scroll +scroll-alloy-consensus = { workspace = true, optional = true, features = ["k256"] }   # op op-alloy-consensus = { workspace = true, optional = true, features = ["k256"] } @@ -95,6 +100,7 @@ "reth-chainspec/std", "revm-bytecode/std", "revm-state/std", "alloy-rpc-types-eth?/std", + "scroll-alloy-consensus?/std", ] secp256k1 = ["alloy-consensus/secp256k1"] test-utils = [ @@ -118,6 +124,7 @@ "op-alloy-consensus?/arbitrary", "alloy-trie/arbitrary", "reth-chainspec/arbitrary", "alloy-rpc-types-eth?/arbitrary", + "scroll-alloy-consensus?/arbitrary", ] serde-bincode-compat = [ "serde", @@ -128,6 +135,7 @@ "op-alloy-consensus?/serde", "op-alloy-consensus?/serde-bincode-compat", "alloy-genesis/serde-bincode-compat", "alloy-rpc-types-eth?/serde-bincode-compat", + "scroll-alloy-consensus?/serde-bincode-compat", ] serde = [ "dep:serde", @@ -146,16 +154,19 @@ "revm-bytecode/serde", "revm-state/serde", "rand_08/serde", "alloy-rpc-types-eth?/serde", + "scroll-alloy-consensus?/serde", ] reth-codec = [ "dep:reth-codecs", "dep:modular-bitfield", "dep:byteorder", + "scroll-alloy-consensus?/reth-codec", ] op = [ "dep:op-alloy-consensus", "reth-codecs?/op", ] +scroll-alloy-traits = ["scroll-alloy-consensus"] rayon = [ "dep:rayon", ]
diff --git reth/crates/primitives-traits/src/block/header.rs scroll-reth/crates/primitives-traits/src/block/header.rs index c4df8ecf542ffecce04b1ff19bcdd5df0af0b94c..38a218d11d1cd4ebe1d208591ab65248e6b58039 100644 --- reth/crates/primitives-traits/src/block/header.rs +++ scroll-reth/crates/primitives-traits/src/block/header.rs @@ -1,7 +1,7 @@ //! Block header data primitive.   use crate::{InMemorySize, MaybeCompact, MaybeSerde, MaybeSerdeBincodeCompat}; -use alloy_primitives::Sealable; +use alloy_primitives::{Bytes, Sealable}; use core::{fmt, hash::Hash};   /// Re-exported alias @@ -33,7 +33,20 @@ + MaybeSerde + MaybeSerdeBincodeCompat + AsRef<Self> + 'static + + BlockHeaderMut { }   impl BlockHeader for alloy_consensus::Header {} + +/// Returns a mutable reference to fields of the header. +pub trait BlockHeaderMut { + /// Mutable reference to the extra data. + fn extra_data_mut(&mut self) -> &mut Bytes; +} + +impl BlockHeaderMut for alloy_consensus::Header { + fn extra_data_mut(&mut self) -> &mut Bytes { + &mut self.extra_data + } +}
diff --git reth/crates/primitives-traits/src/block/recovered.rs scroll-reth/crates/primitives-traits/src/block/recovered.rs index fd2acea1d000801e114277c33205bae7f4ec0d33..5f60f317b6161733812eae9ee09ff735f42bf656 100644 --- reth/crates/primitives-traits/src/block/recovered.rs +++ scroll-reth/crates/primitives-traits/src/block/recovered.rs @@ -78,6 +78,7 @@ Self { block: SealedBlock::new_unhashed(block), senders } }   /// Returns the recovered senders. + #[allow(clippy::missing_const_for_fn)] pub fn senders(&self) -> &[Address] { &self.senders }
diff --git reth/crates/primitives-traits/src/serde_bincode_compat.rs scroll-reth/crates/primitives-traits/src/serde_bincode_compat.rs index 217ad5ff3320b447f0c246d67123a7c78ae8d930..fcfb7f681e7ea1cfddd33848d1a013bb89a77561 100644 --- reth/crates/primitives-traits/src/serde_bincode_compat.rs +++ scroll-reth/crates/primitives-traits/src/serde_bincode_compat.rs @@ -345,4 +345,17 @@ fn from_repr(repr: Self::BincodeRepr<'_>) -> Self { repr.into() } } + + #[cfg(feature = "scroll-alloy-traits")] + impl SerdeBincodeCompat for scroll_alloy_consensus::ScrollTxEnvelope { + type BincodeRepr<'a> = scroll_alloy_consensus::serde_bincode_compat::ScrollTxEnvelope<'a>; + + fn as_repr(&self) -> Self::BincodeRepr<'_> { + self.into() + } + + fn from_repr(repr: Self::BincodeRepr<'_>) -> Self { + repr.into() + } + } }
diff --git reth/crates/primitives-traits/src/size.rs scroll-reth/crates/primitives-traits/src/size.rs index 734ae78b191985612ad506acaefd99ef97154060..21b192b6bc7328ad5202968847fb9606e9095481 100644 --- reth/crates/primitives-traits/src/size.rs +++ scroll-reth/crates/primitives-traits/src/size.rs @@ -173,6 +173,49 @@ } } } } + +/// Implementations for scroll types. +#[cfg(feature = "scroll-alloy-traits")] +mod scroll { + use super::*; + use scroll_alloy_consensus::ScrollTxEnvelope; + + impl InMemorySize for scroll_alloy_consensus::ScrollTypedTransaction { + fn size(&self) -> usize { + match self { + Self::Legacy(tx) => tx.size(), + Self::Eip2930(tx) => tx.size(), + Self::Eip1559(tx) => tx.size(), + Self::Eip7702(tx) => tx.size(), + Self::L1Message(tx) => tx.size(), + } + } + } + + impl InMemorySize for scroll_alloy_consensus::ScrollPooledTransaction { + fn size(&self) -> usize { + match self { + Self::Legacy(tx) => tx.size(), + Self::Eip2930(tx) => tx.size(), + Self::Eip1559(tx) => tx.size(), + Self::Eip7702(tx) => tx.size(), + } + } + } + + impl InMemorySize for ScrollTxEnvelope { + fn size(&self) -> usize { + match self { + Self::Legacy(tx) => tx.size(), + Self::Eip2930(tx) => tx.size(), + Self::Eip1559(tx) => tx.size(), + Self::Eip7702(tx) => tx.size(), + Self::L1Message(tx) => tx.size(), + } + } + } +} + #[cfg(test)] mod tests { use super::*;
diff --git reth/crates/primitives-traits/src/transaction/signed.rs scroll-reth/crates/primitives-traits/src/transaction/signed.rs index 104555db0f7c25781d43414ecb804abf16a28ab4..d45edc3031b47a8e3585903af0cb8c724a322af6 100644 --- reth/crates/primitives-traits/src/transaction/signed.rs +++ scroll-reth/crates/primitives-traits/src/transaction/signed.rs @@ -175,3 +175,32 @@ } } } } + +#[cfg(feature = "scroll-alloy-traits")] +mod scroll { + use super::*; + use scroll_alloy_consensus::{ScrollPooledTransaction, ScrollTxEnvelope}; + + impl SignedTransaction for ScrollPooledTransaction { + fn tx_hash(&self) -> &TxHash { + match self { + Self::Legacy(tx) => tx.hash(), + Self::Eip2930(tx) => tx.hash(), + Self::Eip1559(tx) => tx.hash(), + Self::Eip7702(tx) => tx.hash(), + } + } + } + + impl SignedTransaction for ScrollTxEnvelope { + fn tx_hash(&self) -> &TxHash { + match self { + Self::Legacy(tx) => tx.hash(), + Self::Eip2930(tx) => tx.hash(), + Self::Eip1559(tx) => tx.hash(), + Self::Eip7702(tx) => tx.hash(), + Self::L1Message(tx) => tx.hash_ref(), + } + } + } +}
diff --git reth/crates/primitives/Cargo.toml scroll-reth/crates/primitives/Cargo.toml index 67fae820d93896427daaaf8ccdb5f03f4708826d..665dcab9a88006249be2639c20128f15e7fd302a 100644 --- reth/crates/primitives/Cargo.toml +++ scroll-reth/crates/primitives/Cargo.toml @@ -27,9 +27,6 @@ # misc once_cell.workspace = true   -# arbitrary utils -arbitrary = { workspace = true, features = ["derive"], optional = true } - [dev-dependencies] # eth reth-primitives-traits = { workspace = true, features = ["arbitrary", "test-utils"] } @@ -70,7 +67,6 @@ asm-keccak = [ "alloy-primitives/asm-keccak", ] arbitrary = [ - "dep:arbitrary", "alloy-eips/arbitrary", "reth-codec", "reth-ethereum-forks/arbitrary",
diff --git reth/crates/stages/stages/benches/criterion.rs scroll-reth/crates/stages/stages/benches/criterion.rs index c804d582363251c1f5af9e9248fe8d05af8748b0..d755f2fdd0449c4cd1c09ba1276517fa392006fa 100644 --- reth/crates/stages/stages/benches/criterion.rs +++ scroll-reth/crates/stages/stages/benches/criterion.rs @@ -5,6 +5,7 @@ use alloy_primitives::BlockNumber; use criterion::{criterion_main, measurement::WallTime, BenchmarkGroup, Criterion}; use reth_config::config::{EtlConfig, TransactionLookupConfig}; use reth_db::{test_utils::TempDatabase, Database, DatabaseEnv}; +use reth_ethereum_primitives::EthPrimitives; use reth_provider::{test_utils::MockNodeTypesWithDB, DatabaseProvider, DatabaseProviderFactory}; use reth_stages::{ stages::{MerkleStage, SenderRecoveryStage, TransactionLookupStage}, @@ -113,7 +114,10 @@ group.sample_size(10);   let db = setup::txs_testdata(DEFAULT_NUM_BLOCKS);   - let stage = MerkleStage::Both { rebuild_threshold: u64::MAX, incremental_threshold: u64::MAX }; + let stage = MerkleStage::<EthPrimitives>::Both { + rebuild_threshold: u64::MAX, + incremental_threshold: u64::MAX, + }; measure_stage( runtime, &mut group, @@ -124,7 +128,8 @@ 1..=DEFAULT_NUM_BLOCKS, "Merkle-incremental".to_string(), );   - let stage = MerkleStage::Both { rebuild_threshold: 0, incremental_threshold: 0 }; + let stage = + MerkleStage::<EthPrimitives>::Both { rebuild_threshold: 0, incremental_threshold: 0 }; measure_stage( runtime, &mut group,
diff --git reth/crates/stages/stages/src/sets.rs scroll-reth/crates/stages/stages/src/sets.rs index 97c3a3116aa1784e6ccdf7e5e44c7eefeb2a347e..30dbd9281ddeb90026e66663c4a59ebfc8c733d7 100644 --- reth/crates/stages/stages/src/sets.rs +++ scroll-reth/crates/stages/stages/src/sets.rs @@ -320,18 +320,21 @@ where E: ConfigureEvm, ExecutionStages<E>: StageSet<Provider>, PruneSenderRecoveryStage: Stage<Provider>, - HashingStages: StageSet<Provider>, + HashingStages<E::Primitives>: StageSet<Provider>, HistoryIndexingStages: StageSet<Provider>, PruneStage: Stage<Provider>, { fn builder(self) -> StageSetBuilder<Provider> { - ExecutionStages::new(self.evm_config, self.consensus, self.stages_config.clone()) + ExecutionStages::new(self.evm_config, self.consensus.clone(), self.stages_config.clone()) .builder() // If sender recovery prune mode is set, add the prune sender recovery stage. .add_stage_opt(self.prune_modes.sender_recovery.map(|prune_mode| { PruneSenderRecoveryStage::new(prune_mode, self.stages_config.prune.commit_threshold) })) - .add_set(HashingStages { stages_config: self.stages_config.clone() }) + .add_set(HashingStages { + stages_config: self.stages_config.clone(), + consensus: self.consensus, + }) .add_set(HistoryIndexingStages { stages_config: self.stages_config.clone(), prune_modes: self.prune_modes.clone(), @@ -387,22 +390,25 @@ } }   /// A set containing all stages that hash account state. -#[derive(Debug, Default)] +#[derive(Debug)] #[non_exhaustive] -pub struct HashingStages { +pub struct HashingStages<P: NodePrimitives> { /// Configuration for each stage in the pipeline stages_config: StageConfig, + /// Consensus instance for validating blocks. + consensus: Arc<dyn FullConsensus<P, Error = ConsensusError>>, }   -impl<Provider> StageSet<Provider> for HashingStages +impl<Provider, P> StageSet<Provider> for HashingStages<P> where - MerkleStage: Stage<Provider>, + P: NodePrimitives, + MerkleStage<P>: Stage<Provider>, AccountHashingStage: Stage<Provider>, StorageHashingStage: Stage<Provider>, { fn builder(self) -> StageSetBuilder<Provider> { StageSetBuilder::default() - .add_stage(MerkleStage::default_unwind()) + .add_stage(MerkleStage::new_unwind(self.consensus.clone())) .add_stage(AccountHashingStage::new( self.stages_config.account_hashing, self.stages_config.etl.clone(), @@ -414,6 +420,7 @@ )) .add_stage(MerkleStage::new_execution( self.stages_config.merkle.rebuild_threshold, self.stages_config.merkle.incremental_threshold, + self.consensus, )) } }
diff --git reth/crates/stages/stages/src/stages/merkle.rs scroll-reth/crates/stages/stages/src/stages/merkle.rs index 54fc5b2477c59108be0b13e2ff2659c62a2e4faf..abaa12c6c6b7c04209401ce35b002200055c02eb 100644 --- reth/crates/stages/stages/src/stages/merkle.rs +++ scroll-reth/crates/stages/stages/src/stages/merkle.rs @@ -1,25 +1,28 @@ -use alloy_consensus::{constants::KECCAK_EMPTY, BlockHeader}; -use alloy_primitives::{BlockNumber, Sealable, B256}; +use alloy_consensus::{constants::KECCAK_EMPTY, BlockHeader as _}; use reth_codecs::Compact; -use reth_consensus::ConsensusError; use reth_db_api::{ tables, transaction::{DbTx, DbTxMut}, }; -use reth_primitives_traits::{GotExpected, SealedHeader}; +use reth_primitives_traits::BlockHeader; use reth_provider::{ DBProvider, HeaderProvider, ProviderError, StageCheckpointReader, StageCheckpointWriter, StatsReader, TrieWriter, }; use reth_stages_api::{ - BlockErrorKind, EntitiesCheckpoint, ExecInput, ExecOutput, MerkleCheckpoint, Stage, - StageCheckpoint, StageError, StageId, StorageRootMerkleCheckpoint, UnwindInput, UnwindOutput, + EntitiesCheckpoint, ExecInput, ExecOutput, MerkleCheckpoint, Stage, StageCheckpoint, + StageError, StageId, StorageRootMerkleCheckpoint, UnwindInput, UnwindOutput, }; use reth_trie::{IntermediateStateRootState, StateRoot, StateRootProgress, StoredSubNode}; use reth_trie_db::DatabaseStateRoot; -use std::fmt::Debug; +use std::{fmt::Debug, sync::Arc}; use tracing::*;   +use alloy_primitives::{BlockNumber, Sealable, B256}; +use reth_consensus::{Consensus, ConsensusError, HeaderValidator}; +use reth_primitives_traits::{NodePrimitives, SealedHeader}; +use reth_stages_api::BlockErrorKind; + // TODO: automate the process outlined below so the user can just send in a debugging package /// The error message that we include in invalid state root errors to tell users what information /// they should include in a bug report, since true state root errors can be impossible to debug @@ -70,7 +73,10 @@ /// - [`AccountHashingStage`][crate::stages::AccountHashingStage] /// - [`StorageHashingStage`][crate::stages::StorageHashingStage] /// - [`MerkleStage::Execution`] #[derive(Debug, Clone)] -pub enum MerkleStage { +pub enum MerkleStage<P> +where + P: NodePrimitives, +{ /// The execution portion of the merkle stage. Execution { // TODO: make struct for holding incremental settings, for code reuse between `Execution` @@ -82,9 +88,14 @@ /// The threshold (in number of blocks) to run the stage in incremental mode. The /// incremental mode will calculate the state root by calculating the new state root for /// some number of blocks, repeating until we reach the desired block number. incremental_threshold: u64, + /// Consensus. + consensus: Arc<dyn Consensus<P::Block, Error = ConsensusError>>, }, /// The unwind portion of the merkle stage. - Unwind, + Unwind { + /// Consensus. + consensus: Arc<dyn Consensus<P::Block, Error = ConsensusError>>, + }, /// Able to execute and unwind. Used for tests #[cfg(any(test, feature = "test-utils"))] Both { @@ -98,23 +109,44 @@ incremental_threshold: u64, }, }   -impl MerkleStage { - /// Stage default for the [`MerkleStage::Execution`]. - pub const fn default_execution() -> Self { +impl<P> MerkleStage<P> +where + P: NodePrimitives, +{ + /// Stage default for the [`MerkleStage::Execution`] with the provided consensus. + pub const fn default_execution_with_consensus( + consensus: Arc<dyn Consensus<P::Block, Error = ConsensusError>>, + ) -> Self { Self::Execution { rebuild_threshold: MERKLE_STAGE_DEFAULT_REBUILD_THRESHOLD, incremental_threshold: MERKLE_STAGE_DEFAULT_INCREMENTAL_THRESHOLD, + consensus, } }   - /// Stage default for the [`MerkleStage::Unwind`]. - pub const fn default_unwind() -> Self { - Self::Unwind + /// Create new instance of [`MerkleStage::Execution`]. + pub const fn new_execution( + rebuild_threshold: u64, + incremental_threshold: u64, + consensus: Arc<dyn Consensus<P::Block, Error = ConsensusError>>, + ) -> Self { + Self::Execution { rebuild_threshold, incremental_threshold, consensus } }   - /// Create new instance of [`MerkleStage::Execution`]. - pub const fn new_execution(rebuild_threshold: u64, incremental_threshold: u64) -> Self { - Self::Execution { rebuild_threshold, incremental_threshold } + /// Create new instance of [`MerkleStage::Unwind`]. + pub const fn new_unwind( + consensus: Arc<dyn Consensus<P::Block, Error = ConsensusError>>, + ) -> Self { + Self::Unwind { consensus } + } + + /// Returns the consensus for the stage. + pub fn consensus(&self) -> Arc<dyn Consensus<P::Block, Error = ConsensusError>> { + match self { + Self::Execution { consensus, .. } | Self::Unwind { consensus } => consensus.clone(), + #[cfg(any(test, feature = "test-utils"))] + Self::Both { .. } => reth_consensus::noop::NoopConsensus::arc(), + } }   /// Gets the hashing progress @@ -152,7 +184,7 @@ Ok(provider.save_stage_checkpoint_progress(StageId::MerkleExecute, buf)?) } }   -impl<Provider> Stage<Provider> for MerkleStage +impl<Provider, P> Stage<Provider> for MerkleStage<P> where Provider: DBProvider<Tx: DbTxMut> + TrieWriter @@ -160,12 +192,13 @@ + StatsReader + HeaderProvider + StageCheckpointReader + StageCheckpointWriter, + P: NodePrimitives<BlockHeader = <Provider as HeaderProvider>::Header>, { /// Return the id of the stage fn id(&self) -> StageId { match self { Self::Execution { .. } => StageId::MerkleExecute, - Self::Unwind => StageId::MerkleUnwind, + Self::Unwind { .. } => StageId::MerkleUnwind, #[cfg(any(test, feature = "test-utils"))] Self::Both { .. } => StageId::Other("MerkleBoth"), } @@ -174,11 +207,11 @@ /// Execute the stage. fn execute(&mut self, provider: &Provider, input: ExecInput) -> Result<ExecOutput, StageError> { let (threshold, incremental_threshold) = match self { - Self::Unwind => { + Self::Unwind { .. } => { info!(target: "sync::stages::merkle::unwind", "Stage is always skipped"); - return Ok(ExecOutput::done(StageCheckpoint::new(input.target()))) + return Ok(ExecOutput::done(StageCheckpoint::new(input.target()))); } - Self::Execution { rebuild_threshold, incremental_threshold } => { + Self::Execution { rebuild_threshold, incremental_threshold, .. } => { (*rebuild_threshold, *incremental_threshold) } #[cfg(any(test, feature = "test-utils"))] @@ -343,7 +376,13 @@ // Reset the checkpoint self.save_execution_checkpoint(provider, None)?;   - validate_state_root(trie_root, SealedHeader::seal_slow(target_block), to_block)?; + // Ensure state root matches + validate_state_root( + self.consensus(), + trie_root, + SealedHeader::seal_slow(target_block), + to_block, + )?;   Ok(ExecOutput { checkpoint: StageCheckpoint::new(to_block) @@ -396,7 +435,13 @@ let target = provider .header_by_number(input.unwind_to)? .ok_or_else(|| ProviderError::HeaderNotFound(input.unwind_to.into()))?;   - validate_state_root(block_root, SealedHeader::seal_slow(target), input.unwind_to)?; + let consensus = self.consensus(); + validate_state_root( + consensus, + block_root, + SealedHeader::seal_slow(target), + input.unwind_to, + )?;   // Validation passed, apply unwind changes to the database. provider.write_trie_updates(&updates)?; @@ -411,21 +456,19 @@ /// Check that the computed state root matches the root in the expected header. #[inline] fn validate_state_root<H: BlockHeader + Sealable + Debug>( + consensus: Arc<dyn HeaderValidator<H>>, got: B256, expected: SealedHeader<H>, target_block: BlockNumber, ) -> Result<(), StageError> { - if got == expected.state_root() { - Ok(()) - } else { + consensus.validate_state_root(&*expected, got).inspect_err(|_|{ error!(target: "sync::stages::merkle", ?target_block, ?got, ?expected, "Failed to verify block state root! {INVALID_STATE_ROOT_ERROR_MESSAGE}"); - Err(StageError::Block { - error: BlockErrorKind::Validation(ConsensusError::BodyStateRootDiff( - GotExpected { got, expected: expected.state_root() }.into(), - )), + }).map_err(|err|{ + StageError::Block { + error: BlockErrorKind::Validation(err), block: Box::new(expected.block_with_parent()), - }) - } + } + }) }   #[cfg(test)] @@ -435,10 +478,11 @@ use crate::test_utils::{ stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, StorageKind, TestRunnerError, TestStageDB, UnwindStageTestRunner, }; - use alloy_primitives::{keccak256, U256}; + use alloy_primitives::{keccak256, B256, U256}; use assert_matches::assert_matches; use reth_db_api::cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO}; - use reth_primitives_traits::{SealedBlock, StorageEntry}; + use reth_ethereum_primitives::EthPrimitives; + use reth_primitives_traits::{SealedBlock, SealedHeader, StorageEntry}; use reth_provider::{providers::StaticFileWriter, StaticFileProviderFactory}; use reth_stages_api::StageUnitCheckpoint; use reth_static_file_types::StaticFileSegment; @@ -609,7 +653,7 @@ } }   impl StageTestRunner for MerkleTestRunner { - type S = MerkleStage; + type S = MerkleStage<EthPrimitives>;   fn db(&self) -> &TestStageDB { &self.db
diff --git reth/crates/stages/stages/src/stages/s3/downloader/fetch.rs scroll-reth/crates/stages/stages/src/stages/s3/downloader/fetch.rs index 9c5e3c3d324139facae0cbf0c62f3ef75aeb5f6f..1715186bffc5aee3dbb9c4773db3a0c638a3215c 100644 --- reth/crates/stages/stages/src/stages/s3/downloader/fetch.rs +++ scroll-reth/crates/stages/stages/src/stages/s3/downloader/fetch.rs @@ -169,7 +169,9 @@ mod tests { use super::*; use alloy_primitives::b256;   + // TODO: Enable this when the test is fixed upstream. #[tokio::test] + #[ignore] async fn test_download() { reth_tracing::init_test_tracing();
diff --git reth/crates/storage/codecs/derive/src/compact/mod.rs scroll-reth/crates/storage/codecs/derive/src/compact/mod.rs index ae349cd06e590f9a630a8005000111fe3a5506a0..0e795e7a94d828553f5541998c1fd27ef389ea19 100644 --- reth/crates/storage/codecs/derive/src/compact/mod.rs +++ scroll-reth/crates/storage/codecs/derive/src/compact/mod.rs @@ -202,7 +202,7 @@ /// length. pub fn get_bit_size(ftype: &str) -> u8 { match ftype { "TransactionKind" | "TxKind" | "bool" | "Option" | "Signature" => 1, - "TxType" | "OpTxType" => 2, + "TxType" | "OpTxType" | "ScrollTxType" => 2, "u64" | "BlockNumber" | "TxNumber" | "ChainId" | "NumTransactions" => 4, "u128" => 5, "U256" => 6,
diff --git reth/crates/storage/db-api/Cargo.toml scroll-reth/crates/storage/db-api/Cargo.toml index 3f7e5c7b1a7ea6ceffb789723529b8b11ad374c9..900dd525f4a17407d6eced7fa91de8cb5f895246 100644 --- reth/crates/storage/db-api/Cargo.toml +++ scroll-reth/crates/storage/db-api/Cargo.toml @@ -27,6 +27,9 @@ alloy-primitives.workspace = true alloy-genesis.workspace = true alloy-consensus.workspace = true   +# scroll +reth-scroll-primitives = { workspace = true, optional = true } + # optimism reth-optimism-primitives = { workspace = true, optional = true }   @@ -84,6 +87,7 @@ "reth-stages-types/arbitrary", "alloy-consensus/arbitrary", "reth-optimism-primitives?/arbitrary", "reth-ethereum-primitives/arbitrary", + "reth-scroll-primitives?/arbitrary", ] op = [ "dep:reth-optimism-primitives", @@ -91,3 +95,4 @@ "reth-codecs/op", "reth-primitives-traits/op", ] bench = [] +scroll-alloy-traits = ["dep:reth-scroll-primitives", "reth-primitives-traits/scroll-alloy-traits"]
diff --git reth/crates/storage/db-api/src/models/mod.rs scroll-reth/crates/storage/db-api/src/models/mod.rs index cffa9d910f85e722327dba610d51c715bc88ce7c..f593662ee4a7d7e49e1c2df6bddb0d2b0666c1e8 100644 --- reth/crates/storage/db-api/src/models/mod.rs +++ scroll-reth/crates/storage/db-api/src/models/mod.rs @@ -245,6 +245,14 @@ impl_compression_for_compact!(OpTransactionSigned, OpReceipt); }   +#[cfg(feature = "scroll-alloy-traits")] +mod scroll { + use super::*; + use reth_scroll_primitives::{ScrollReceipt, ScrollTransactionSigned}; + + impl_compression_for_compact!(ScrollTransactionSigned, ScrollReceipt); +} + macro_rules! impl_compression_fixed_compact { ($($name:tt),+) => { $(
diff --git reth/crates/storage/db-api/src/tables/raw.rs scroll-reth/crates/storage/db-api/src/tables/raw.rs index 96208a25d56ba3e68d5e122ab72d544af91246ba..bbe33101e2392e13d351ed574e2b8bea3c7dd630 100644 --- reth/crates/storage/db-api/src/tables/raw.rs +++ scroll-reth/crates/storage/db-api/src/tables/raw.rs @@ -136,6 +136,7 @@ V::decompress(&self.value) }   /// Returns the raw value as seen on the database. + #[allow(clippy::missing_const_for_fn)] pub fn raw_value(&self) -> &[u8] { &self.value }
diff --git reth/crates/storage/db/Cargo.toml scroll-reth/crates/storage/db/Cargo.toml index 719c7b785c14a4db3b3f2af2a9873101cfd9d86d..df48b0b86eccc2148fa27a54138cc19beb496b82 100644 --- reth/crates/storage/db/Cargo.toml +++ scroll-reth/crates/storage/db/Cargo.toml @@ -93,6 +93,7 @@ op = [ "reth-db-api/op", "reth-primitives-traits/op", ] +scroll-alloy-traits = ["reth-db-api/scroll-alloy-traits", "reth-primitives-traits/scroll-alloy-traits"] disable-lock = []   [[bench]]
diff --git reth/crates/storage/db/src/lib.rs scroll-reth/crates/storage/db/src/lib.rs index 278986f9f9835d9466062e7109187221cd61dea8..2ed0a951f8ecb874b1b6dd6afd6ff9fa1ef15831 100644 --- reth/crates/storage/db/src/lib.rs +++ scroll-reth/crates/storage/db/src/lib.rs @@ -108,6 +108,7 @@ self.db.as_ref().unwrap() }   /// Returns the path to the database. + #[allow(clippy::missing_const_for_fn)] pub fn path(&self) -> &Path { &self.path }
diff --git reth/crates/storage/libmdbx-rs/src/environment.rs scroll-reth/crates/storage/libmdbx-rs/src/environment.rs index 648526a7fc503e6396a3792756648382d99bbd89..f4a639cb341e95b8e3d1c6f04889e34ffbdcbbc7 100644 --- reth/crates/storage/libmdbx-rs/src/environment.rs +++ scroll-reth/crates/storage/libmdbx-rs/src/environment.rs @@ -60,12 +60,14 @@ }   /// Returns true if the environment was opened as WRITEMAP. #[inline] + #[allow(clippy::missing_const_for_fn)] pub fn is_write_map(&self) -> bool { self.inner.env_kind.is_write_map() }   /// Returns the kind of the environment. #[inline] + #[allow(clippy::missing_const_for_fn)] pub fn env_kind(&self) -> EnvironmentKind { self.inner.env_kind } @@ -84,6 +86,7 @@ }   /// Returns the transaction manager. #[inline] + #[allow(clippy::missing_const_for_fn)] pub(crate) fn txn_manager(&self) -> &TxnManager { &self.inner.txn_manager } @@ -131,6 +134,7 @@ /// /// The caller **must** ensure that the pointer is never dereferenced after the environment has /// been dropped. #[inline] + #[allow(clippy::missing_const_for_fn)] pub(crate) fn env_ptr(&self) -> *mut ffi::MDBX_env { self.inner.env }
diff --git reth/crates/storage/libmdbx-rs/src/transaction.rs scroll-reth/crates/storage/libmdbx-rs/src/transaction.rs index a19e70956604ce2884009e49f1872ef0221324cb..677a33474b020bba24f011cc3b042b40ea0a4cf3 100644 --- reth/crates/storage/libmdbx-rs/src/transaction.rs +++ scroll-reth/crates/storage/libmdbx-rs/src/transaction.rs @@ -130,11 +130,13 @@ /// Returns a copy of the raw pointer to the underlying MDBX transaction. #[doc(hidden)] #[cfg(test)] + #[allow(clippy::missing_const_for_fn)] pub fn txn(&self) -> *mut ffi::MDBX_txn { self.inner.txn.txn }   /// Returns a raw pointer to the MDBX environment. + #[allow(clippy::missing_const_for_fn)] pub fn env(&self) -> &Environment { &self.inner.env }
diff --git reth/crates/storage/nippy-jar/src/lib.rs scroll-reth/crates/storage/nippy-jar/src/lib.rs index f3d1944d3b49123a571693bb00f77261ad700c3b..daa272fdd1f40a3225623516aeb75894dbec84eb 100644 --- reth/crates/storage/nippy-jar/src/lib.rs +++ scroll-reth/crates/storage/nippy-jar/src/lib.rs @@ -413,6 +413,7 @@ &self.data_mmap[range] }   /// Returns total size of data + #[allow(clippy::missing_const_for_fn)] pub fn size(&self) -> usize { self.data_mmap.len() }
diff --git reth/crates/storage/nippy-jar/src/writer.rs scroll-reth/crates/storage/nippy-jar/src/writer.rs index 1069c6e67da2cacc5d03293303efbc1bd097647b..52bdd8b2e64bf9f4e6154617ece0f0ad6c85b0f1 100644 --- reth/crates/storage/nippy-jar/src/writer.rs +++ scroll-reth/crates/storage/nippy-jar/src/writer.rs @@ -430,6 +430,7 @@ }   /// Returns a reference to the offsets vector. #[cfg(test)] + #[allow(clippy::missing_const_for_fn)] pub fn offsets(&self) -> &[u64] { &self.offsets }
diff --git reth/crates/storage/provider/src/providers/database/provider.rs scroll-reth/crates/storage/provider/src/providers/database/provider.rs index 848ad45f0877cbb011518e30665b15edbf808343..646b81cf8591b9d31e2a946b1855d8186e225da4 100644 --- reth/crates/storage/provider/src/providers/database/provider.rs +++ scroll-reth/crates/storage/provider/src/providers/database/provider.rs @@ -279,7 +279,7 @@ }   // Unwind account history indices. self.unwind_account_history_indices(changed_accounts.iter())?; - let storage_range = BlockNumberAddress::range(range.clone()); + let storage_range = BlockNumberAddress::range(range);   let changed_storages = self .tx @@ -311,29 +311,11 @@ account_prefix_set: account_prefix_set.freeze(), storage_prefix_sets, destroyed_accounts, }; - let (new_state_root, trie_updates) = StateRoot::from_tx(&self.tx) + let (_, trie_updates) = StateRoot::from_tx(&self.tx) .with_prefix_sets(prefix_sets) .root_with_updates() .map_err(reth_db_api::DatabaseError::from)?;   - let parent_number = range.start().saturating_sub(1); - let parent_state_root = self - .header_by_number(parent_number)? - .ok_or_else(|| ProviderError::HeaderNotFound(parent_number.into()))? - .state_root(); - - // state root should be always correct as we are reverting state. - // but for sake of double verification we will check it again. - if new_state_root != parent_state_root { - let parent_hash = self - .block_hash(parent_number)? - .ok_or_else(|| ProviderError::HeaderNotFound(parent_number.into()))?; - return Err(ProviderError::UnwindStateRootMismatch(Box::new(RootMismatch { - root: GotExpected { got: new_state_root, expected: parent_state_root }, - block_number: parent_number, - block_hash: parent_hash, - }))) - } self.write_trie_updates(&trie_updates)?;   Ok(()) @@ -543,6 +525,7 @@ &self.tx }   /// Returns a reference to the chain specification. + #[allow(clippy::missing_const_for_fn)] pub fn chain_spec(&self) -> &N::ChainSpec { &self.chain_spec }
diff --git reth/crates/storage/provider/src/providers/mod.rs scroll-reth/crates/storage/provider/src/providers/mod.rs index 36843a22fba930ee031daf743e01ec18cd619e4b..5575d9b1ffa0cb84df28dde52c8da634f767b137 100644 --- reth/crates/storage/provider/src/providers/mod.rs +++ scroll-reth/crates/storage/provider/src/providers/mod.rs @@ -1,6 +1,6 @@ //! Contains the main provider types and traits for interacting with the blockchain's storage.   -use reth_chainspec::EthereumHardforks; +use reth_chainspec::EthereumCapabilities; use reth_db_api::table::Value; use reth_node_types::{FullNodePrimitives, NodeTypes, NodeTypesWithDB};   @@ -33,7 +33,7 @@ /// [`ProviderNodeTypes`]. pub trait NodeTypesForProvider where Self: NodeTypes< - ChainSpec: EthereumHardforks, + ChainSpec: EthereumCapabilities, Storage: ChainStorage<Self::Primitives>, Primitives: FullNodePrimitives<SignedTx: Value, Receipt: Value, BlockHeader: Value>, >, @@ -42,7 +42,7 @@ }   impl<T> NodeTypesForProvider for T where T: NodeTypes< - ChainSpec: EthereumHardforks, + ChainSpec: EthereumCapabilities, Storage: ChainStorage<T::Primitives>, Primitives: FullNodePrimitives<SignedTx: Value, Receipt: Value, BlockHeader: Value>, >
diff --git reth/crates/storage/provider/src/providers/static_file/manager.rs scroll-reth/crates/storage/provider/src/providers/static_file/manager.rs index 721c11c656410201745abcf3cf38cb76430d1b57..d66d2cdf2ce1df8c368fd5194e7a9a6f494c192c 100644 --- reth/crates/storage/provider/src/providers/static_file/manager.rs +++ scroll-reth/crates/storage/provider/src/providers/static_file/manager.rs @@ -1214,6 +1214,7 @@ })) }   /// Returns directory where `static_files` are located. + #[allow(clippy::missing_const_for_fn)] pub fn directory(&self) -> &Path { &self.path } @@ -1305,12 +1306,14 @@ }   /// Returns `static_files` directory #[cfg(any(test, feature = "test-utils"))] + #[allow(clippy::missing_const_for_fn)] pub fn path(&self) -> &Path { &self.path }   /// Returns `static_files` transaction index #[cfg(any(test, feature = "test-utils"))] + #[allow(clippy::missing_const_for_fn)] pub fn tx_index(&self) -> &RwLock<SegmentRanges> { &self.static_files_tx_index }
diff --git reth/crates/storage/provider/src/test_utils/mock.rs scroll-reth/crates/storage/provider/src/test_utils/mock.rs index 68f8c38e59dd349e92fa6b13c1b2df10b83815b1..7636b27db4a7e75fe034378c87a9a5e0ecde423b 100644 --- reth/crates/storage/provider/src/test_utils/mock.rs +++ scroll-reth/crates/storage/provider/src/test_utils/mock.rs @@ -5,7 +5,7 @@ ChainSpecProvider, ChangeSetReader, EthStorage, HeaderProvider, ReceiptProviderIdExt, StateProvider, StateProviderBox, StateProviderFactory, StateReader, StateRootProvider, TransactionVariant, TransactionsProvider, }; -use alloy_consensus::{constants::EMPTY_ROOT_HASH, transaction::TransactionMeta, Header}; +use alloy_consensus::{constants::EMPTY_ROOT_HASH, transaction::TransactionMeta, BlockHeader}; use alloy_eips::{BlockHashOrNumber, BlockId, BlockNumberOrTag}; use alloy_primitives::{ keccak256, map::HashMap, Address, BlockHash, BlockNumber, Bytes, StorageKey, StorageValue, @@ -19,11 +19,12 @@ mock::{DatabaseMock, TxMock}, models::{AccountBeforeTx, StoredBlockBodyIndices}, }; use reth_ethereum_engine_primitives::EthEngineTypes; -use reth_ethereum_primitives::{EthPrimitives, Receipt}; +use reth_ethereum_primitives::EthPrimitives; use reth_execution_types::ExecutionOutcome; use reth_node_types::NodeTypes; use reth_primitives_traits::{ - Account, Bytecode, GotExpected, NodePrimitives, RecoveredBlock, SealedHeader, SignerRecoverable, + Account, Block, BlockBody, Bytecode, GotExpected, NodePrimitives, RecoveredBlock, SealedHeader, + SignedTransaction, SignerRecoverable, }; use reth_prune_types::PruneModes; use reth_stages_types::{StageCheckpoint, StageId}; @@ -48,16 +49,14 @@ use tokio::sync::broadcast;   /// A mock implementation for Provider interfaces. #[derive(Debug)] -pub struct MockEthProvider< - T: NodePrimitives = reth_ethereum_primitives::EthPrimitives, - ChainSpec = reth_chainspec::ChainSpec, -> { +pub struct MockEthProvider<T: NodePrimitives = EthPrimitives, ChainSpec = reth_chainspec::ChainSpec> +{ ///local block store pub blocks: Arc<Mutex<HashMap<B256, T::Block>>>, /// Local header store - pub headers: Arc<Mutex<HashMap<B256, Header>>>, + pub headers: Arc<Mutex<HashMap<B256, <T::Block as Block>::Header>>>, /// Local receipt store indexed by block number - pub receipts: Arc<Mutex<HashMap<BlockNumber, Vec<Receipt>>>>, + pub receipts: Arc<Mutex<HashMap<BlockNumber, Vec<T::Receipt>>>>, /// Local account store pub accounts: Arc<Mutex<HashMap<Address, ExtendedAccount>>>, /// Local chain spec @@ -106,31 +105,31 @@ } } }   -impl<ChainSpec> MockEthProvider<reth_ethereum_primitives::EthPrimitives, ChainSpec> { +impl<T: NodePrimitives, ChainSpec> MockEthProvider<T, ChainSpec> { /// Add block to local block store - pub fn add_block(&self, hash: B256, block: reth_ethereum_primitives::Block) { - self.add_header(hash, block.header.clone()); + pub fn add_block(&self, hash: B256, block: T::Block) { + self.add_header(hash, block.header().clone()); self.blocks.lock().insert(hash, block); }   /// Add multiple blocks to local block store - pub fn extend_blocks( - &self, - iter: impl IntoIterator<Item = (B256, reth_ethereum_primitives::Block)>, - ) { + pub fn extend_blocks(&self, iter: impl IntoIterator<Item = (B256, T::Block)>) { for (hash, block) in iter { - self.add_header(hash, block.header.clone()); + self.add_header(hash, block.header().clone()); self.add_block(hash, block) } }   /// Add header to local header store - pub fn add_header(&self, hash: B256, header: Header) { + pub fn add_header(&self, hash: B256, header: <T::Block as Block>::Header) { self.headers.lock().insert(hash, header); }   /// Add multiple headers to local header store - pub fn extend_headers(&self, iter: impl IntoIterator<Item = (B256, Header)>) { + pub fn extend_headers( + &self, + iter: impl IntoIterator<Item = (B256, <T::Block as Block>::Header)>, + ) { for (hash, header) in iter { self.add_header(hash, header) } @@ -149,12 +148,12 @@ } }   /// Add receipts to local receipt store - pub fn add_receipts(&self, block_number: BlockNumber, receipts: Vec<Receipt>) { + pub fn add_receipts(&self, block_number: BlockNumber, receipts: Vec<T::Receipt>) { self.receipts.lock().insert(block_number, receipts); }   /// Add multiple receipts to local receipt store - pub fn extend_receipts(&self, iter: impl IntoIterator<Item = (BlockNumber, Vec<Receipt>)>) { + pub fn extend_receipts(&self, iter: impl IntoIterator<Item = (BlockNumber, Vec<T::Receipt>)>) { for (block_number, receipts) in iter { self.add_receipts(block_number, receipts); } @@ -175,10 +174,7 @@ self.state_roots.lock().push(state_root); }   /// Set chain spec. - pub fn with_chain_spec<C>( - self, - chain_spec: C, - ) -> MockEthProvider<reth_ethereum_primitives::EthPrimitives, C> { + pub fn with_chain_spec<C>(self, chain_spec: C) -> MockEthProvider<T, C> { MockEthProvider { blocks: self.blocks, headers: self.headers, @@ -300,27 +296,27 @@ &self.prune_modes } }   -impl<ChainSpec: EthChainSpec + Send + Sync + 'static> HeaderProvider - for MockEthProvider<reth_ethereum_primitives::EthPrimitives, ChainSpec> +impl<T: NodePrimitives, ChainSpec: EthChainSpec + Send + Sync + 'static> HeaderProvider + for MockEthProvider<T, ChainSpec> { - type Header = Header; + type Header = <T::Block as Block>::Header;   - fn header(&self, block_hash: &BlockHash) -> ProviderResult<Option<Header>> { + fn header(&self, block_hash: &BlockHash) -> ProviderResult<Option<Self::Header>> { let lock = self.headers.lock(); Ok(lock.get(block_hash).cloned()) }   - fn header_by_number(&self, num: u64) -> ProviderResult<Option<Header>> { + fn header_by_number(&self, num: u64) -> ProviderResult<Option<Self::Header>> { let lock = self.headers.lock(); - Ok(lock.values().find(|h| h.number == num).cloned()) + Ok(lock.values().find(|h| h.number() == num).cloned()) }   fn header_td(&self, hash: &BlockHash) -> ProviderResult<Option<U256>> { let lock = self.headers.lock(); Ok(lock.get(hash).map(|target| { lock.values() - .filter(|h| h.number < target.number) - .fold(target.difficulty, |td, h| td + h.difficulty) + .filter(|h| h.number() < target.number()) + .fold(target.difficulty(), |td, h| td + h.difficulty()) })) }   @@ -328,30 +324,36 @@ fn header_td_by_number(&self, number: BlockNumber) -> ProviderResult<Option<U256>> { let lock = self.headers.lock(); let sum = lock .values() - .filter(|h| h.number <= number) - .fold(U256::ZERO, |td, h| td + h.difficulty); + .filter(|h| h.number() <= number) + .fold(U256::ZERO, |td, h| td + h.difficulty()); Ok(Some(sum)) }   - fn headers_range(&self, range: impl RangeBounds<BlockNumber>) -> ProviderResult<Vec<Header>> { + fn headers_range( + &self, + range: impl RangeBounds<BlockNumber>, + ) -> ProviderResult<Vec<Self::Header>> { let lock = self.headers.lock();   let mut headers: Vec<_> = - lock.values().filter(|header| range.contains(&header.number)).cloned().collect(); - headers.sort_by_key(|header| header.number); + lock.values().filter(|header| range.contains(&header.number())).cloned().collect(); + headers.sort_by_key(|header| header.number());   Ok(headers) }   - fn sealed_header(&self, number: BlockNumber) -> ProviderResult<Option<SealedHeader>> { + fn sealed_header( + &self, + number: BlockNumber, + ) -> ProviderResult<Option<SealedHeader<Self::Header>>> { Ok(self.header_by_number(number)?.map(SealedHeader::seal_slow)) }   fn sealed_headers_while( &self, range: impl RangeBounds<BlockNumber>, - mut predicate: impl FnMut(&SealedHeader) -> bool, - ) -> ProviderResult<Vec<SealedHeader>> { + mut predicate: impl FnMut(&SealedHeader<Self::Header>) -> bool, + ) -> ProviderResult<Vec<SealedHeader<Self::Header>>> { Ok(self .headers_range(range)? .into_iter() @@ -373,16 +375,16 @@ self.chain_spec.clone() } }   -impl<ChainSpec: EthChainSpec + 'static> TransactionsProvider - for MockEthProvider<reth_ethereum_primitives::EthPrimitives, ChainSpec> +impl<T: NodePrimitives, ChainSpec: EthChainSpec + 'static> TransactionsProvider + for MockEthProvider<T, ChainSpec> { - type Transaction = reth_ethereum_primitives::TransactionSigned; + type Transaction = T::SignedTx;   fn transaction_id(&self, tx_hash: TxHash) -> ProviderResult<Option<TxNumber>> { let lock = self.blocks.lock(); let tx_number = lock .values() - .flat_map(|block| &block.body.transactions) + .flat_map(|block| block.body().transactions()) .position(|tx| *tx.tx_hash() == tx_hash) .map(|pos| pos as TxNumber);   @@ -392,7 +394,7 @@ fn transaction_by_id(&self, id: TxNumber) -> ProviderResult<Option<Self::Transaction>> { let lock = self.blocks.lock(); let transaction = - lock.values().flat_map(|block| &block.body.transactions).nth(id as usize).cloned(); + lock.values().flat_map(|block| block.body().transactions()).nth(id as usize).cloned();   Ok(transaction) } @@ -403,14 +405,14 @@ id: TxNumber, ) -> ProviderResult<Option<Self::Transaction>> { let lock = self.blocks.lock(); let transaction = - lock.values().flat_map(|block| &block.body.transactions).nth(id as usize).cloned(); + lock.values().flat_map(|block| block.body().transactions()).nth(id as usize).cloned();   Ok(transaction) }   fn transaction_by_hash(&self, hash: TxHash) -> ProviderResult<Option<Self::Transaction>> { Ok(self.blocks.lock().iter().find_map(|(_, block)| { - block.body.transactions.iter().find(|tx| *tx.tx_hash() == hash).cloned() + block.body().transactions_iter().find(|tx| *tx.tx_hash() == hash).cloned() })) }   @@ -420,16 +422,16 @@ hash: TxHash, ) -> ProviderResult<Option<(Self::Transaction, TransactionMeta)>> { let lock = self.blocks.lock(); for (block_hash, block) in lock.iter() { - for (index, tx) in block.body.transactions.iter().enumerate() { + for (index, tx) in block.body().transactions_iter().enumerate() { if *tx.tx_hash() == hash { let meta = TransactionMeta { tx_hash: hash, index: index as u64, block_hash: *block_hash, - block_number: block.header.number, - base_fee: block.header.base_fee_per_gas, - excess_blob_gas: block.header.excess_blob_gas, - timestamp: block.header.timestamp, + block_number: block.header().number(), + base_fee: block.header().base_fee_per_gas(), + excess_blob_gas: block.header().excess_blob_gas(), + timestamp: block.header().timestamp(), }; return Ok(Some((tx.clone(), meta))) } @@ -442,10 +444,10 @@ fn transaction_block(&self, id: TxNumber) -> ProviderResult<Option<BlockNumber>> { let lock = self.blocks.lock(); let mut current_tx_number: TxNumber = 0; for block in lock.values() { - if current_tx_number + (block.body.transactions.len() as TxNumber) > id { - return Ok(Some(block.header.number)) + if current_tx_number + (block.body().transaction_count() as TxNumber) > id { + return Ok(Some(block.header().number())) } - current_tx_number += block.body.transactions.len() as TxNumber; + current_tx_number += block.body().transaction_count() as TxNumber; } Ok(None) } @@ -454,7 +456,7 @@ fn transactions_by_block( &self, id: BlockHashOrNumber, ) -> ProviderResult<Option<Vec<Self::Transaction>>> { - Ok(self.block(id)?.map(|b| b.body.transactions)) + Ok(self.block(id)?.map(|b| b.body().clone_transactions())) }   fn transactions_by_block_range( @@ -464,8 +466,8 @@ ) -> ProviderResult<Vec<Vec<Self::Transaction>>> { // init btreemap so we can return in order let mut map = BTreeMap::new(); for (_, block) in self.blocks.lock().iter() { - if range.contains(&block.number) { - map.insert(block.number, block.body.transactions.clone()); + if range.contains(&block.header().number()) { + map.insert(block.header().number(), block.body().clone_transactions()); } }   @@ -479,7 +481,7 @@ ) -> ProviderResult<Vec<Self::Transaction>> { let lock = self.blocks.lock(); let transactions = lock .values() - .flat_map(|block| &block.body.transactions) + .flat_map(|block| block.body().transactions()) .enumerate() .filter(|&(tx_number, _)| range.contains(&(tx_number as TxNumber))) .map(|(_, tx)| tx.clone()) @@ -495,7 +497,7 @@ ) -> ProviderResult<Vec<Address>> { let lock = self.blocks.lock(); let transactions = lock .values() - .flat_map(|block| &block.body.transactions) + .flat_map(|block| block.body().transactions()) .enumerate() .filter_map(|(tx_number, tx)| { if range.contains(&(tx_number as TxNumber)) { @@ -519,7 +521,7 @@ where T: NodePrimitives, ChainSpec: Send + Sync + 'static, { - type Receipt = Receipt; + type Receipt = T::Receipt;   fn receipt(&self, _id: TxNumber) -> ProviderResult<Option<Self::Receipt>> { Ok(None) @@ -540,7 +542,7 @@ BlockHashOrNumber::Hash(hash) => { // Find block number by hash first let headers_lock = self.headers.lock(); if let Some(header) = headers_lock.get(&hash) { - Ok(receipts_lock.get(&header.number).cloned()) + Ok(receipts_lock.get(&header.number()).cloned()) } else { Ok(None) } @@ -566,7 +568,7 @@ let mut result = Vec::new(); for block_number in block_range { // Only include blocks that exist in headers (i.e., have been added to the provider) - if headers_lock.values().any(|header| header.number == block_number) { + if headers_lock.values().any(|header| header.number() == block_number) { if let Some(block_receipts) = receipts_lock.get(&block_number) { result.push(block_receipts.clone()); } else { @@ -593,7 +595,7 @@ { fn block_hash(&self, number: u64) -> ProviderResult<Option<B256>> { let lock = self.headers.lock(); let hash = - lock.iter().find_map(|(hash, header)| (header.number == number).then_some(*hash)); + lock.iter().find_map(|(hash, header)| (header.number() == number).then_some(*hash)); Ok(hash) }   @@ -604,9 +606,9 @@ end: BlockNumber, ) -> ProviderResult<Vec<B256>> { let lock = self.headers.lock(); let mut hashes: Vec<_> = - lock.iter().filter(|(_, header)| (start..end).contains(&header.number)).collect(); + lock.iter().filter(|(_, header)| (start..end).contains(&header.number())).collect();   - hashes.sort_by_key(|(_, header)| header.number); + hashes.sort_by_key(|(_, header)| header.number());   Ok(hashes.into_iter().map(|(hash, _)| *hash).collect()) } @@ -621,16 +623,16 @@ let lock = self.headers.lock();   Ok(lock .iter() - .find(|(_, header)| header.number == best_block_number) - .map(|(hash, header)| ChainInfo { best_hash: *hash, best_number: header.number }) + .find(|(_, header)| header.number() == best_block_number) + .map(|(hash, header)| ChainInfo { best_hash: *hash, best_number: header.number() }) .unwrap_or_default()) }   fn best_block_number(&self) -> ProviderResult<BlockNumber> { let lock = self.headers.lock(); lock.iter() - .max_by_key(|h| h.1.number) - .map(|(_, header)| header.number) + .max_by_key(|h| h.1.number()) + .map(|(_, header)| header.number()) .ok_or(ProviderError::BestBlockNotFound) }   @@ -640,7 +642,7 @@ }   fn block_number(&self, hash: B256) -> ProviderResult<Option<alloy_primitives::BlockNumber>> { let lock = self.headers.lock(); - Ok(lock.get(&hash).map(|header| header.number)) + Ok(lock.get(&hash).map(|header| header.number())) } }   @@ -661,10 +663,10 @@ } }   //look -impl<ChainSpec: EthChainSpec + Send + Sync + 'static> BlockReader - for MockEthProvider<reth_ethereum_primitives::EthPrimitives, ChainSpec> +impl<T: NodePrimitives, ChainSpec: EthChainSpec + Send + Sync + 'static> BlockReader + for MockEthProvider<T, ChainSpec> { - type Block = reth_ethereum_primitives::Block; + type Block = T::Block;   fn find_block_by_hash( &self, @@ -678,7 +680,9 @@ fn block(&self, id: BlockHashOrNumber) -> ProviderResult<Option<Self::Block>> { let lock = self.blocks.lock(); match id { BlockHashOrNumber::Hash(hash) => Ok(lock.get(&hash).cloned()), - BlockHashOrNumber::Number(num) => Ok(lock.values().find(|b| b.number == num).cloned()), + BlockHashOrNumber::Number(num) => { + Ok(lock.values().find(|b| b.header().number() == num).cloned()) + } } }   @@ -688,7 +692,7 @@ }   fn pending_block_and_receipts( &self, - ) -> ProviderResult<Option<(RecoveredBlock<Self::Block>, Vec<Receipt>)>> { + ) -> ProviderResult<Option<(RecoveredBlock<Self::Block>, Vec<T::Receipt>)>> { Ok(None) }   @@ -711,9 +715,12 @@ fn block_range(&self, range: RangeInclusive<BlockNumber>) -> ProviderResult<Vec<Self::Block>> { let lock = self.blocks.lock();   - let mut blocks: Vec<_> = - lock.values().filter(|block| range.contains(&block.number)).cloned().collect(); - blocks.sort_by_key(|block| block.number); + let mut blocks: Vec<_> = lock + .values() + .filter(|block| range.contains(&block.header().number())) + .cloned() + .collect(); + blocks.sort_by_key(|block| block.header().number());   Ok(blocks) } @@ -733,26 +740,29 @@ Ok(vec![]) } }   -impl<ChainSpec> BlockReaderIdExt - for MockEthProvider<reth_ethereum_primitives::EthPrimitives, ChainSpec> +impl<T, ChainSpec> BlockReaderIdExt for MockEthProvider<T, ChainSpec> where ChainSpec: EthChainSpec + Send + Sync + 'static, + T: NodePrimitives, { - fn block_by_id(&self, id: BlockId) -> ProviderResult<Option<reth_ethereum_primitives::Block>> { + fn block_by_id(&self, id: BlockId) -> ProviderResult<Option<T::Block>> { match id { BlockId::Number(num) => self.block_by_number_or_tag(num), BlockId::Hash(hash) => self.block_by_hash(hash.block_hash), } }   - fn sealed_header_by_id(&self, id: BlockId) -> ProviderResult<Option<SealedHeader>> { + fn sealed_header_by_id( + &self, + id: BlockId, + ) -> ProviderResult<Option<SealedHeader<<T::Block as Block>::Header>>> { self.header_by_id(id)?.map_or_else(|| Ok(None), |h| Ok(Some(SealedHeader::seal_slow(h)))) }   - fn header_by_id(&self, id: BlockId) -> ProviderResult<Option<Header>> { + fn header_by_id(&self, id: BlockId) -> ProviderResult<Option<<T::Block as Block>::Header>> { match self.block_by_id(id)? { None => Ok(None), - Some(block) => Ok(Some(block.header)), + Some(block) => Ok(Some(block.into_header())), } } } @@ -989,9 +999,12 @@ } }   impl<T: NodePrimitives, ChainSpec: Send + Sync> StateReader for MockEthProvider<T, ChainSpec> { - type Receipt = Receipt; + type Receipt = T::Receipt;   - fn get_state(&self, _block: BlockNumber) -> ProviderResult<Option<ExecutionOutcome>> { + fn get_state( + &self, + _block: BlockNumber, + ) -> ProviderResult<Option<ExecutionOutcome<Self::Receipt>>> { Ok(None) } } @@ -1019,7 +1032,7 @@ use reth_ethereum_primitives::Receipt;   #[test] fn test_mock_provider_receipts() { - let provider = MockEthProvider::new(); + let provider = MockEthProvider::<EthPrimitives>::new();   let block_hash = BlockHash::random(); let block_number = 1u64; @@ -1050,7 +1063,7 @@ }   #[test] fn test_mock_provider_receipts_multiple_blocks() { - let provider = MockEthProvider::new(); + let provider = MockEthProvider::<EthPrimitives>::new();   let block1_hash = BlockHash::random(); let block2_hash = BlockHash::random();
diff --git reth/crates/storage/storage-api/src/base_fee.rs scroll-reth/crates/storage/storage-api/src/base_fee.rs new file mode 100644 index 0000000000000000000000000000000000000000..35415a613716aad0a104759430ac2621a0c0e4e6 --- /dev/null +++ scroll-reth/crates/storage/storage-api/src/base_fee.rs @@ -0,0 +1,57 @@ +use crate::{StateProvider, StateProviderBox}; + +use alloy_consensus::BlockHeader; +use alloy_primitives::{Address, U256}; +use reth_chainspec::EthChainSpec; +use reth_storage_errors::ProviderError; +use revm_database::{Database, State}; + +/// An instance of the trait can return the base fee for the next block. +pub trait BaseFeeProvider<P: StorageProvider> { + /// Returns the base fee for the next block. + fn next_block_base_fee<H: BlockHeader>( + &self, + provider: &mut P, + parent_header: &H, + ts: u64, + ) -> Result<u64, P::Error>; +} + +impl<T: EthChainSpec, P: StorageProvider> BaseFeeProvider<P> for T { + fn next_block_base_fee<H: BlockHeader>( + &self, + _provider: &mut P, + parent_header: &H, + ts: u64, + ) -> Result<u64, P::Error> { + Ok(parent_header + .next_block_base_fee(self.base_fee_params_at_timestamp(ts)) + .unwrap_or_default()) + } +} + +/// A storage provider trait that can be implemented on foreign types. +pub trait StorageProvider { + /// The error type. + type Error; + + /// Returns the storage value at the address for the provided key. + fn storage(&mut self, address: Address, key: U256) -> Result<U256, Self::Error>; +} + +impl<DB: Database> StorageProvider for State<DB> { + type Error = DB::Error; + + fn storage(&mut self, address: Address, key: U256) -> Result<U256, Self::Error> { + let _ = self.load_cache_account(address)?; + Database::storage(self, address, key) + } +} + +impl StorageProvider for StateProviderBox { + type Error = ProviderError; + + fn storage(&mut self, address: Address, key: U256) -> Result<U256, Self::Error> { + Ok(StateProvider::storage(self, address, key.into())?.unwrap_or_default()) + } +}
diff --git reth/crates/storage/storage-api/src/chain.rs scroll-reth/crates/storage/storage-api/src/chain.rs index 5c66d055e1808ccca8e9534eb6316a4803f396d5..ffc0c8cb3c93ebd035460b9aa8139d583f6f6fdd 100644 --- reth/crates/storage/storage-api/src/chain.rs +++ scroll-reth/crates/storage/storage-api/src/chain.rs @@ -3,7 +3,7 @@ use alloc::vec::Vec; use alloy_consensus::Header; use alloy_primitives::BlockNumber; use core::marker::PhantomData; -use reth_chainspec::{ChainSpecProvider, EthereumHardforks}; +use reth_chainspec::{ChainSpecProvider, EthereumCapabilities, EthereumHardforks}; use reth_db_api::{ cursor::{DbCursorRO, DbCursorRW}, models::StoredBlockOmmers, @@ -146,7 +146,7 @@ }   impl<Provider, T, H> BlockBodyReader<Provider> for EthStorage<T, H> where - Provider: DBProvider + ChainSpecProvider<ChainSpec: EthereumHardforks>, + Provider: DBProvider + ChainSpecProvider<ChainSpec: EthereumCapabilities>, T: SignedTransaction, H: FullBlockHeader, { @@ -167,7 +167,7 @@ for (header, transactions) in inputs { // If we are past shanghai, then all blocks should have a withdrawal list, // even if empty - let withdrawals = if chain_spec.is_shanghai_active_at_timestamp(header.timestamp()) { + let withdrawals = if chain_spec.withdrawals_active(header.timestamp()) { withdrawals_cursor .seek_exact(header.number())? .map(|(_, w)| w.withdrawals)
diff --git reth/crates/storage/storage-api/src/lib.rs scroll-reth/crates/storage/storage-api/src/lib.rs index 7b326c6c82e910453e63420e52d4f7fd28807fa6..71e0fdc010279db384f2fcea382542017e7e5733 100644 --- reth/crates/storage/storage-api/src/lib.rs +++ scroll-reth/crates/storage/storage-api/src/lib.rs @@ -82,6 +82,9 @@ mod primitives; pub use primitives::*;   +mod base_fee; +pub use base_fee::*; + mod block_indices; pub use block_indices::*;
diff --git reth/crates/trie/common/src/updates.rs scroll-reth/crates/trie/common/src/updates.rs index a752fd06d73f90af74a69c5395d4bd9d8872d2ac..de5ee3ef740ce42db04d3c824330fd144301d124 100644 --- reth/crates/trie/common/src/updates.rs +++ scroll-reth/crates/trie/common/src/updates.rs @@ -435,6 +435,7 @@ }   impl TrieUpdatesSorted { /// Returns reference to updated account nodes. + #[allow(clippy::missing_const_for_fn)] pub fn account_nodes_ref(&self) -> &[(Nibbles, BranchNodeCompact)] { &self.account_nodes } @@ -481,6 +482,7 @@ self.is_deleted }   /// Returns reference to updated storage nodes. + #[allow(clippy::missing_const_for_fn)] pub fn storage_nodes_ref(&self) -> &[(Nibbles, BranchNodeCompact)] { &self.storage_nodes }
diff --git reth/crates/trie/trie/Cargo.toml scroll-reth/crates/trie/trie/Cargo.toml index adee3291b805b1c355d714df96f9c234ea3b9732..68f6e9640bc381f5c55eea32e9b8dcca9d627c1b 100644 --- reth/crates/trie/trie/Cargo.toml +++ scroll-reth/crates/trie/trie/Cargo.toml @@ -18,7 +18,7 @@ reth-primitives-traits.workspace = true reth-stages-types.workspace = true reth-storage-errors.workspace = true reth-trie-sparse.workspace = true -reth-trie-common = { workspace = true, features = ["rayon"] } +reth-trie-common.workspace = true   revm-database.workspace = true   @@ -64,6 +64,8 @@ proptest-arbitrary-interop.workspace = true proptest.workspace = true   [features] +default = ["rayon"] +rayon = ["reth-trie-common/rayon"] metrics = ["reth-metrics", "dep:metrics"] serde = [ "alloy-primitives/serde", @@ -90,6 +92,7 @@ "reth-trie-common/test-utils", "reth-ethereum-primitives/test-utils", "reth-trie-sparse/test-utils", "reth-stages-types/test-utils", + "reth-primitives-traits/test-utils", ]   [[bench]]
diff --git reth/.config/zepter.yaml scroll-reth/.config/zepter.yaml index b754d06a062c0a45dcbd02ea103ac05ccaa43f55..3fb82bec823f2ef4cb5a1c6f37478212de9560ed 100644 --- reth/.config/zepter.yaml +++ scroll-reth/.config/zepter.yaml @@ -8,14 +8,14 @@ # The examples in the following comments assume crate `A` to have a dependency on crate `B`. workflows: check: - [ - "lint", - # Check that `A` activates the features of `B`. - "propagate-feature", - # These are the features to check: - "--features=std,op,dev,asm-keccak,jemalloc,jemalloc-prof,tracy-allocator,serde-bincode-compat,serde,test-utils,arbitrary,bench,alloy-compat", - # Do not try to add a new section to `[features]` of `A` only because `B` exposes that feature. There are edge-cases where this is still needed, but we can add them manually. - "--left-side-feature-missing=ignore", - # Ignore the case that `A` it outside of the workspace. Otherwise it will report errors in external dependencies that we have no influence on. + "lint", + # Check that `A` activates the features of `B`. + "propagate-feature", + # These are the features to check: + "--features=std,op,scroll-alloy-traits,dev,asm-keccak,jemalloc,jemalloc-prof,tracy-allocator,serde-bincode-compat,serde,test-utils,arbitrary,bench,alloy-compat", + # Do not try to add a new section to `[features]` of `A` only because `B` exposes that feature. There are edge-cases where this is still needed, but we can add them manually. + "--left-side-feature-missing=ignore", + # Ignore the case that `A` it outside of the workspace. Otherwise it will report errors in external dependencies that we have no influence on.   "--left-side-outside-workspace=ignore", # Auxiliary flags:
diff --git reth/.gitignore scroll-reth/.gitignore index a9b9f4768d53b8b82007f2ee9f87cfa8b87f8314..894ca5f346b71dfad74d9797c0da4bdd0bf44629 100644 --- reth/.gitignore +++ scroll-reth/.gitignore @@ -2,6 +2,7 @@ # Generated by Cargo # will have compiled files and executables ./debug/ target/ +datadir/   # These are backup files generated by rustfmt **/*.rs.bk
diff --git reth/Cargo.lock scroll-reth/Cargo.lock index 67bf3ed0e4108d510b267ca80976dd6fa5ea42b6..b3a68a1cbdf690f55a3fe701636da3f3b109a5a6 100644 --- reth/Cargo.lock +++ scroll-reth/Cargo.lock @@ -853,7 +853,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78e3736701b5433afd06eecff08f0688a71a10e0e1352e0bbf0bed72f0dd4e35" dependencies = [ "alloy-json-rpc", + "alloy-rpc-types-engine", "alloy-transport", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "jsonwebtoken", "reqwest", "serde_json", "tower", @@ -2423,6 +2429,16 @@ ]   [[package]] name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" @@ -3140,6 +3156,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"   [[package]] +name = "encoder-standard" +version = "0.1.0" +source = "git+https://github.com/scroll-tech/da-codec#2cfec8c99547b68dc64e2b020fa2b83cfb9c0e99" +dependencies = [ + "zstd", +] + +[[package]] name = "enr" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3192,6 +3216,17 @@ "syn 2.0.104", ]   [[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3425,7 +3460,6 @@ "alloy-rpc-types-engine", "alloy-rpc-types-eth", "alloy-serde", "async-trait", - "derive_more", "eyre", "jsonrpsee", "modular-bitfield", @@ -3443,6 +3477,7 @@ "reth-node-builder", "reth-op", "reth-optimism-forks", "reth-payload-builder", + "reth-primitives-traits", "reth-rpc-api", "reth-rpc-engine-api", "revm", @@ -3820,6 +3855,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"   [[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4447,6 +4497,22 @@ "webpki-roots 1.0.2", ]   [[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] name = "hyper-util" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4465,9 +4531,11 @@ "libc", "percent-encoding", "pin-project-lite", "socket2 0.6.0", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ]   [[package]] @@ -5810,6 +5878,23 @@ "unsigned-varint", ]   [[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + +[[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -6145,8 +6230,7 @@ [[package]] name = "op-revm" version = "8.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce1dc7533f4e5716c55cd3d62488c6200cb4dfda96e0c75a7e484652464343b" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" dependencies = [ "auto_impl", "once_cell", @@ -6161,12 +6245,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"   [[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"   [[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] name = "opentelemetry" version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -7641,6 +7763,7 @@ "reth-ethereum-primitives", "reth-optimism-primitives", "reth-primitives-traits", "reth-prune-types", + "reth-scroll-primitives", "reth-stages-types", "reth-storage-errors", "reth-trie-common", @@ -7925,6 +8048,7 @@ "reth-payload-builder", "reth-payload-primitives", "reth-provider", "reth-transaction-pool", + "scroll-alloy-rpc-types-engine", "tokio", "tokio-stream", "tracing", @@ -8412,6 +8536,7 @@ "reth-storage-api", "reth-storage-errors", "reth-trie-common", "revm", + "scroll-alloy-evm", ]   [[package]] @@ -8758,6 +8883,7 @@ "reth-ethereum-forks", "reth-network-p2p", "reth-network-peers", "reth-network-types", + "reth-primitives-traits", "reth-tokio-util", "serde", "thiserror 2.0.12", @@ -9585,6 +9711,7 @@ "reth-chain-state", "reth-chainspec", "reth-errors", "reth-primitives-traits", + "scroll-alloy-rpc-types-engine", "serde", "thiserror 2.0.12", "tokio", @@ -9660,6 +9787,7 @@ "reth-codecs", "revm-bytecode", "revm-primitives", "revm-state", + "scroll-alloy-consensus", "secp256k1 0.30.0", "serde", "serde_json", @@ -9895,6 +10023,7 @@ "reth-rpc-engine-api", "reth-rpc-eth-api", "reth-rpc-eth-types", "reth-rpc-server-types", + "reth-scroll-evm", "reth-storage-api", "reth-tasks", "reth-testing-utils", @@ -10035,8 +10164,13 @@ "reth-ethereum-primitives", "reth-evm", "reth-optimism-primitives", "reth-primitives-traits", + "reth-scroll-primitives", "reth-storage-api", "revm-context", + "revm-scroll", + "scroll-alloy-consensus", + "scroll-alloy-evm", + "scroll-alloy-rpc-types", "thiserror 2.0.12", ]   @@ -10129,6 +10263,7 @@ "reth-revm", "reth-rpc-convert", "reth-rpc-eth-types", "reth-rpc-server-types", + "reth-scroll-evm", "reth-storage-api", "reth-tasks", "reth-transaction-pool", @@ -10216,6 +10351,314 @@ "strum 0.27.2", ]   [[package]] +name = "reth-scroll-chainspec" +version = "1.6.0" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-serde", + "auto_impl", + "derive_more", + "once_cell", + "reth-chainspec", + "reth-ethereum-forks", + "reth-network-peers", + "reth-primitives-traits", + "reth-scroll-forks", + "reth-trie-common", + "scroll-alloy-hardforks", + "serde", + "serde_json", +] + +[[package]] +name = "reth-scroll-cli" +version = "1.6.0" +dependencies = [ + "clap", + "eyre", + "proptest", + "reth-cli", + "reth-cli-commands", + "reth-cli-runner", + "reth-consensus", + "reth-db", + "reth-node-builder", + "reth-node-core", + "reth-node-metrics", + "reth-scroll-chainspec", + "reth-scroll-consensus", + "reth-scroll-evm", + "reth-scroll-node", + "reth-scroll-primitives", + "reth-tracing", + "scroll-alloy-consensus", + "tracing", +] + +[[package]] +name = "reth-scroll-consensus" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "reth-chainspec", + "reth-consensus", + "reth-consensus-common", + "reth-ethereum-consensus", + "reth-execution-types", + "reth-primitives-traits", + "reth-scroll-chainspec", + "reth-scroll-primitives", + "scroll-alloy-consensus", + "scroll-alloy-hardforks", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "reth-scroll-engine-primitives" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-engine", + "arbitrary", + "eyre", + "rand 0.9.2", + "reth-chain-state", + "reth-chainspec", + "reth-engine-primitives", + "reth-payload-builder", + "reth-payload-primitives", + "reth-primitives", + "reth-primitives-traits", + "reth-scroll-chainspec", + "reth-scroll-primitives", + "scroll-alloy-hardforks", + "scroll-alloy-rpc-types-engine", + "serde", + "sha2 0.10.9", +] + +[[package]] +name = "reth-scroll-evm" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-primitives", + "alloy-rpc-types-engine", + "derive_more", + "eyre", + "reth-chainspec", + "reth-evm", + "reth-execution-types", + "reth-primitives", + "reth-primitives-traits", + "reth-scroll-chainspec", + "reth-scroll-forks", + "reth-scroll-primitives", + "reth-storage-api", + "revm", + "revm-primitives", + "revm-scroll", + "scroll-alloy-consensus", + "scroll-alloy-evm", + "scroll-alloy-hardforks", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "reth-scroll-forks" +version = "1.6.0" +dependencies = [ + "alloy-chains", + "alloy-primitives", + "auto_impl", + "once_cell", + "reth-ethereum-forks", + "scroll-alloy-hardforks", + "serde", +] + +[[package]] +name = "reth-scroll-node" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-genesis", + "alloy-primitives", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "clap", + "eyre", + "reth-chainspec", + "reth-db", + "reth-e2e-test-utils", + "reth-engine-local", + "reth-eth-wire-types", + "reth-evm", + "reth-network", + "reth-node-api", + "reth-node-builder", + "reth-node-core", + "reth-node-types", + "reth-payload-builder", + "reth-primitives", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-rpc-eth-types", + "reth-rpc-server-types", + "reth-scroll-chainspec", + "reth-scroll-consensus", + "reth-scroll-engine-primitives", + "reth-scroll-evm", + "reth-scroll-node", + "reth-scroll-payload", + "reth-scroll-primitives", + "reth-scroll-rpc", + "reth-scroll-txpool", + "reth-tasks", + "reth-tracing", + "reth-transaction-pool", + "reth-trie-db", + "revm", + "scroll-alloy-consensus", + "scroll-alloy-evm", + "scroll-alloy-hardforks", + "scroll-alloy-network", + "scroll-alloy-rpc-types-engine", + "serde_json", + "tokio", + "tracing", +] + +[[package]] +name = "reth-scroll-payload" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rlp", + "futures-util", + "reth-basic-payload-builder", + "reth-chain-state", + "reth-chainspec", + "reth-evm", + "reth-execution-types", + "reth-payload-builder", + "reth-payload-primitives", + "reth-payload-util", + "reth-primitives-traits", + "reth-revm", + "reth-scroll-chainspec", + "reth-scroll-engine-primitives", + "reth-scroll-evm", + "reth-scroll-primitives", + "reth-storage-api", + "reth-transaction-pool", + "revm", + "scroll-alloy-hardforks", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "reth-scroll-primitives" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "arbitrary", + "bytes", + "modular-bitfield", + "once_cell", + "rand 0.9.2", + "reth-codecs", + "reth-primitives-traits", + "reth-zstd-compressors", + "rstest", + "scroll-alloy-consensus", + "serde", +] + +[[package]] +name = "reth-scroll-rpc" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-primitives", + "alloy-rpc-client", + "alloy-rpc-types-eth", + "alloy-transport", + "alloy-transport-http", + "eyre", + "jsonrpsee-types", + "reqwest", + "reth-chainspec", + "reth-evm", + "reth-node-api", + "reth-node-builder", + "reth-primitives-traits", + "reth-provider", + "reth-rpc", + "reth-rpc-convert", + "reth-rpc-eth-api", + "reth-rpc-eth-types", + "reth-scroll-chainspec", + "reth-scroll-evm", + "reth-scroll-primitives", + "reth-tasks", + "reth-transaction-pool", + "revm", + "scroll-alloy-consensus", + "scroll-alloy-hardforks", + "scroll-alloy-network", + "scroll-alloy-rpc-types", + "thiserror 2.0.12", + "tokio", + "tracing", +] + +[[package]] +name = "reth-scroll-txpool" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "c-kzg", + "derive_more", + "parking_lot", + "reth-chainspec", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-scroll-chainspec", + "reth-scroll-consensus", + "reth-scroll-evm", + "reth-scroll-forks", + "reth-scroll-primitives", + "reth-storage-api", + "reth-transaction-pool", + "revm-scroll", + "scroll-alloy-consensus", +] + +[[package]] name = "reth-stages" version = "1.6.0" dependencies = [ @@ -10550,7 +10993,7 @@ "reth-provider", "reth-storage-api", "reth-tasks", "reth-tracing", - "revm-interpreter 23.0.2", + "revm-interpreter", "revm-primitives", "rustc-hash 2.1.1", "schnellru", @@ -10755,17 +11198,16 @@ [[package]] name = "revm" version = "27.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6bf82101a1ad8a2b637363a37aef27f88b4efc8a6e24c72bf5f64923dc5532" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" dependencies = [ "revm-bytecode", "revm-context", - "revm-context-interface 9.0.0", + "revm-context-interface", "revm-database", "revm-database-interface", "revm-handler", "revm-inspector", - "revm-interpreter 24.0.0", + "revm-interpreter", "revm-precompile", "revm-primitives", "revm-state", @@ -10773,11 +11215,11 @@ ]   [[package]] name = "revm-bytecode" -version = "6.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70db41f111d3a17362b8bb4ca4c3a77469f9742162add3152838ef6aff523019" +version = "6.1.0" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" dependencies = [ "bitvec", + "once_cell", "phf", "revm-primitives", "serde", @@ -10786,29 +11228,12 @@ [[package]] name = "revm-context" version = "8.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cd508416a35a4d8a9feaf5ccd06ac6d6661cd31ee2dc0252f9f7316455d71f9" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" dependencies = [ "cfg-if", "derive-where", "revm-bytecode", - "revm-context-interface 9.0.0", - "revm-database-interface", - "revm-primitives", - "revm-state", - "serde", -] - -[[package]] -name = "revm-context-interface" -version = "8.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a303a93102fceccec628265efd550ce49f2817b38ac3a492c53f7d524f18a1ca" -dependencies = [ - "alloy-eip2930", - "alloy-eip7702", - "auto_impl", - "either", + "revm-context-interface", "revm-database-interface", "revm-primitives", "revm-state", @@ -10818,8 +11243,7 @@ [[package]] name = "revm-context-interface" version = "9.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc90302642d21c8f93e0876e201f3c5f7913c4fcb66fb465b0fd7b707dfe1c79" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -10833,9 +11257,8 @@ ]   [[package]] name = "revm-database" -version = "7.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b66e2bc5924f60aa7233a0e2994337e636ff08f72e0e35e99755612dab1b8bd" +version = "7.0.2" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" dependencies = [ "alloy-eips", "revm-bytecode", @@ -10847,9 +11270,8 @@ ]   [[package]] name = "revm-database-interface" -version = "7.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2659511acc5c6d5b3cde1908fbe0108981abe8bbf3a94a78d4a4317eb1807293" +version = "7.0.2" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" dependencies = [ "auto_impl", "either", @@ -10861,16 +11283,15 @@ [[package]] name = "revm-handler" version = "8.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1529c8050e663be64010e80ec92bf480315d21b1f2dbf65540028653a621b27d" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" dependencies = [ "auto_impl", "derive-where", "revm-bytecode", "revm-context", - "revm-context-interface 9.0.0", + "revm-context-interface", "revm-database-interface", - "revm-interpreter 24.0.0", + "revm-interpreter", "revm-precompile", "revm-primitives", "revm-state", @@ -10880,15 +11301,14 @@ [[package]] name = "revm-inspector" version = "8.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78db140e332489094ef314eaeb0bd1849d6d01172c113ab0eb6ea8ab9372926" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" dependencies = [ "auto_impl", "either", "revm-context", "revm-database-interface", "revm-handler", - "revm-interpreter 24.0.0", + "revm-interpreter", "revm-primitives", "revm-state", "serde", @@ -10917,24 +11337,11 @@ ]   [[package]] name = "revm-interpreter" -version = "23.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95c4a9a1662d10b689b66b536ddc2eb1e89f5debfcabc1a2d7b8417a2fa47cd" -dependencies = [ - "revm-bytecode", - "revm-context-interface 8.0.1", - "revm-primitives", - "serde", -] - -[[package]] -name = "revm-interpreter" version = "24.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff9d7d9d71e8a33740b277b602165b6e3d25fff091ba3d7b5a8d373bf55f28a7" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" dependencies = [ "revm-bytecode", - "revm-context-interface 9.0.0", + "revm-context-interface", "revm-primitives", "serde", ] @@ -10942,8 +11349,7 @@ [[package]] name = "revm-precompile" version = "25.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cee3f336b83621294b4cfe84d817e3eef6f3d0fce00951973364cc7f860424d" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -10968,21 +11374,32 @@ ]   [[package]] name = "revm-primitives" -version = "20.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e62b900e249a4fc6904d9a76417a3acb711086e3a0ca325da77567f35d46a087" +version = "20.1.0" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" dependencies = [ "alloy-primitives", "num_enum", + "serde", +] + +[[package]] +name = "revm-scroll" +version = "0.1.0" +source = "git+https://github.com/scroll-tech/scroll-revm#720ee7802e5ad695ac1f8699bbab9c9f2424417f" +dependencies = [ + "auto_impl", + "enumn", "once_cell", + "revm", + "revm-inspector", + "revm-primitives", "serde", ]   [[package]] name = "revm-state" -version = "7.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f6ed349ee07a1d015307ff0f10f00660be93032ff4c6d9e72a79a84b8cb5101" +version = "7.0.2" +source = "git+https://github.com/scroll-tech/revm#9cd9896c06a5bf6d4212906260d8789579873ba4" dependencies = [ "bitflags 2.9.1", "revm-bytecode", @@ -11266,7 +11683,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.3.0", ]   [[package]] @@ -11285,7 +11702,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" dependencies = [ - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "jni", "log", @@ -11294,7 +11711,7 @@ "rustls", "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework", + "security-framework 3.3.0", "security-framework-sys", "webpki-root-certs 0.26.11", "windows-sys 0.59.0", @@ -11413,6 +11830,155 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"   [[package]] +name = "scroll-alloy-consensus" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "arbitrary", + "bincode 1.3.3", + "derive_more", + "modular-bitfield", + "proptest", + "proptest-arbitrary-interop", + "rand 0.9.2", + "reth-codecs", + "reth-codecs-derive", + "serde", + "serde_json", + "serde_with", + "test-fuzz", +] + +[[package]] +name = "scroll-alloy-evm" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-hardforks", + "alloy-primitives", + "auto_impl", + "encoder-standard", + "eyre", + "reth-evm", + "reth-scroll-chainspec", + "reth-scroll-evm", + "revm", + "revm-scroll", + "scroll-alloy-consensus", + "scroll-alloy-hardforks", + "serde", +] + +[[package]] +name = "scroll-alloy-hardforks" +version = "1.6.0" +dependencies = [ + "alloy-hardforks", + "auto_impl", + "serde", +] + +[[package]] +name = "scroll-alloy-network" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-eth", + "alloy-signer", + "scroll-alloy-consensus", + "scroll-alloy-rpc-types", +] + +[[package]] +name = "scroll-alloy-provider" +version = "1.6.0" +dependencies = [ + "alloy-primitives", + "alloy-provider", + "alloy-rpc-client", + "alloy-rpc-types-engine", + "alloy-transport", + "alloy-transport-http", + "async-trait", + "derive_more", + "eyre", + "futures-util", + "http-body-util", + "jsonrpsee", + "reqwest", + "reth-engine-primitives", + "reth-payload-builder", + "reth-payload-primitives", + "reth-primitives", + "reth-primitives-traits", + "reth-provider", + "reth-rpc-api", + "reth-rpc-builder", + "reth-rpc-engine-api", + "reth-scroll-chainspec", + "reth-scroll-engine-primitives", + "reth-scroll-node", + "reth-scroll-payload", + "reth-tasks", + "reth-tracing", + "reth-transaction-pool", + "scroll-alloy-network", + "scroll-alloy-rpc-types-engine", + "thiserror 2.0.12", + "tokio", + "tower", +] + +[[package]] +name = "scroll-alloy-rpc-types" +version = "1.6.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "arbitrary", + "derive_more", + "scroll-alloy-consensus", + "serde", + "serde_json", + "similar-asserts", +] + +[[package]] +name = "scroll-alloy-rpc-types-engine" +version = "1.6.0" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-engine", + "arbitrary", + "serde", + "serde_json", +] + +[[package]] +name = "scroll-reth" +version = "1.6.0" +dependencies = [ + "clap", + "reth-cli-util", + "reth-scroll-cli", + "reth-scroll-node", + "tracing", +] + +[[package]] name = "sec1" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -11470,12 +12036,25 @@ ]   [[package]] name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" dependencies = [ "bitflags 2.9.1", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -12053,6 +12632,27 @@ "windows 0.57.0", ]   [[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] name = "tagptr" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -12396,6 +12996,16 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.104", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", ]   [[package]] @@ -13469,6 +14079,17 @@ checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ "windows-core 0.61.2", "windows-link", +] + +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link", + "windows-result 0.3.4", + "windows-strings 0.4.2", ]   [[package]]
diff --git reth/Cargo.toml scroll-reth/Cargo.toml index eda87fdea4729518e9405094bf12fc70c8570ac0..550eea1dda9ec82df5a479a8e48b8ec0531335d9 100644 --- reth/Cargo.toml +++ scroll-reth/Cargo.toml @@ -109,6 +109,25 @@ "crates/rpc/rpc-testing-util/", "crates/rpc/rpc-e2e-tests/", "crates/rpc/rpc-convert/", "crates/rpc/rpc/", + "crates/scroll/alloy/consensus", + "crates/scroll/alloy/evm", + "crates/scroll/alloy/hardforks", + "crates/scroll/alloy/network", + "crates/scroll/alloy/provider", + "crates/scroll/alloy/rpc-types", + "crates/scroll/alloy/rpc-types-engine", + "crates/scroll/bin/scroll-reth", + "crates/scroll/chainspec", + "crates/scroll/cli", + "crates/scroll/consensus", + "crates/scroll/engine-primitives", + "crates/scroll/evm", + "crates/scroll/hardforks", + "crates/scroll/node", + "crates/scroll/payload", + "crates/scroll/primitives", + "crates/scroll/txpool", + "crates/scroll/rpc", "crates/stages/api/", "crates/stages/stages/", "crates/stages/types/", @@ -445,7 +464,7 @@ reth-testing-utils = { path = "testing/testing-utils" } reth-tokio-util = { path = "crates/tokio-util" } reth-tracing = { path = "crates/tracing" } reth-transaction-pool = { path = "crates/transaction-pool" } -reth-trie = { path = "crates/trie/trie" } +reth-trie = { path = "crates/trie/trie", default-features = false } reth-trie-common = { path = "crates/trie/common", default-features = false } reth-trie-db = { path = "crates/trie/db" } reth-trie-parallel = { path = "crates/trie/parallel" } @@ -456,17 +475,18 @@ reth-ress-protocol = { path = "crates/ress/protocol" } reth-ress-provider = { path = "crates/ress/provider" }   # revm -revm = { version = "27.0.3", default-features = false } -revm-bytecode = { version = "6.0.1", default-features = false } -revm-database = { version = "7.0.1", default-features = false } -revm-state = { version = "7.0.1", default-features = false } -revm-primitives = { version = "20.0.0", default-features = false } -revm-interpreter = { version = "23.0.1", default-features = false } -revm-inspector = { version = "8.0.2", default-features = false } -revm-context = { version = "8.0.2", default-features = false } -revm-context-interface = { version = "8.0.1", default-features = false } -revm-database-interface = { version = "7.0.1", default-features = false } -op-revm = { version = "8.0.3", default-features = false } +revm = { git = "https://github.com/scroll-tech/revm", default-features = false, features = ["enable_eip7702", "enable_eip7623"] } +revm-bytecode = { git = "https://github.com/scroll-tech/revm", default-features = false } +revm-database = { git = "https://github.com/scroll-tech/revm", default-features = false } +revm-state = { git = "https://github.com/scroll-tech/revm", default-features = false } +revm-primitives = { git = "https://github.com/scroll-tech/revm", default-features = false } +revm-interpreter = { git = "https://github.com/scroll-tech/revm", default-features = false } +revm-inspector = { git = "https://github.com/scroll-tech/revm", default-features = false } +revm-context = { git = "https://github.com/scroll-tech/revm", default-features = false } +revm-context-interface = { git = "https://github.com/scroll-tech/revm", default-features = false } +revm-database-interface = { git = "https://github.com/scroll-tech/revm", default-features = false } +op-revm = { git = "https://github.com/scroll-tech/revm", default-features = false } +revm-scroll = { git = "https://github.com/scroll-tech/scroll-revm", default-features = false } revm-inspectors = "0.27.1"   # eth @@ -510,6 +530,27 @@ alloy-transport-http = { version = "1.0.23", features = ["reqwest-rustls-tls"], default-features = false } alloy-transport-ipc = { version = "1.0.23", default-features = false } alloy-transport-ws = { version = "1.0.23", default-features = false }   +# scroll +scroll-alloy-consensus = { path = "crates/scroll/alloy/consensus", default-features = false } +scroll-alloy-evm = { path = "crates/scroll/alloy/evm", default-features = false } +scroll-alloy-hardforks = { path = "crates/scroll/alloy/hardforks", default-features = false } +scroll-alloy-network = { path = "crates/scroll/alloy/network", default-features = false } +scroll-alloy-rpc-types = { path = "crates/scroll/alloy/rpc-types", default-features = false } +scroll-alloy-rpc-types-engine = { path = "crates/scroll/alloy/rpc-types-engine", default-features = false } +scroll-alloy-provider = { path = "crates/scroll/alloy/provider" } +reth-scroll-chainspec = { path = "crates/scroll/chainspec", default-features = false } +reth-scroll-cli = { path = "crates/scroll/cli" } +reth-scroll-consensus = { path = "crates/scroll/consensus" } +reth-scroll-evm = { path = "crates/scroll/evm", default-features = false } +reth-scroll-engine-primitives = { path = "crates/scroll/engine-primitives" } +reth-scroll-forks = { path = "crates/scroll/hardforks", default-features = false } +reth-scroll-node = { path = "crates/scroll/node" } +reth-scroll-payload = { path = "crates/scroll/payload" } +reth-scroll-primitives = { path = "crates/scroll/primitives", default-features = false } +reth-scroll-rpc = { path = "crates/scroll/rpc" } +reth-scroll-trie = { path = "crates/scroll/trie" } +reth-scroll-txpool = { path = "crates/scroll/txpool" } + # op alloy-op-evm = { version = "0.17", default-features = false } alloy-op-hardforks = "0.2.2" @@ -523,7 +564,7 @@ # misc either = { version = "1.15.0", default-features = false } aquamarine = "0.6" -auto_impl = "1" +auto_impl = { version = "1", default-features = false } backon = { version = "1.2", default-features = false, features = ["std-blocking-sleep", "tokio-sleep"] } bincode = "1.3" bitflags = "2.4" @@ -574,6 +615,7 @@ mini-moka = "0.10" tar-no-std = { version = "0.3.2", default-features = false } miniz_oxide = { version = "0.8.4", default-features = false } chrono = "0.4.41" +encoder-standard = { git = "https://github.com/scroll-tech/da-codec", default-features = false }   # metrics metrics = "0.24.0" @@ -714,40 +756,42 @@ visibility = "0.1.1" walkdir = "2.3.3" vergen-git2 = "1.0.5"   -# [patch.crates-io] -# alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -# alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } - -# op-alloy-consensus = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" } -# op-alloy-network = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" } -# op-alloy-rpc-types = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" } -# op-alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" } -# op-alloy-rpc-jsonrpsee = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" } +[patch.crates-io] +revm = { git = "https://github.com/scroll-tech/revm" } +op-revm = { git = "https://github.com/scroll-tech/revm" } +# alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } +# +# op-alloy-consensus = { git = "https://github.com/alloy-rs/op-alloy", rev = "ad607c1" } +# op-alloy-network = { git = "https://github.com/alloy-rs/op-alloy", rev = "ad607c1" } +# op-alloy-rpc-types = { git = "https://github.com/alloy-rs/op-alloy", rev = "ad607c1" } +# op-alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/op-alloy", rev = "ad607c1" } +# op-alloy-rpc-jsonrpsee = { git = "https://github.com/alloy-rs/op-alloy", rev = "ad607c1" } # # revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "1207e33" } #
diff --git reth/Cross.toml scroll-reth/Cross.toml index 9b4fd44f75248c31f2591afc76289148786111ef..eff8da32fdef48acc4e92fd81e4c78460f0285b4 100644 --- reth/Cross.toml +++ scroll-reth/Cross.toml @@ -34,5 +34,14 @@ env.passthrough = [ "CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER=riscv64-linux-gnu-gcc", ]   +[target.x86_64-pc-windows-gnu] +# Why do we need a custom Dockerfile on Windows: +# 1. `reth-libmdbx` stopped working with MinGW 9.3 that cross image comes with. +# 2. To be able to update the version of MinGW, we need to also update the Ubuntu that the image is based on. +# +# Also see https://github.com/cross-rs/cross/issues/1667 +# Inspired by https://github.com/cross-rs/cross/blob/9e2298e17170655342d3248a9c8ac37ef92ba38f/docker/Dockerfile.x86_64-pc-windows-gnu#L51 +dockerfile = "./Dockerfile.x86_64-pc-windows-gnu" + [build.env] passthrough = ["JEMALLOC_SYS_WITH_LG_PAGE"]
diff --git reth/Makefile scroll-reth/Makefile index 5dbe2191282b57fa8514b1627a8ddc450318709b..9d54f4f905c8afccf2eebcb91a5cfc6ce960ce10 100644 --- reth/Makefile +++ scroll-reth/Makefile @@ -55,6 +55,12 @@ --features "$(FEATURES)" \ --profile "$(PROFILE)" \ $(CARGO_INSTALL_EXTRA_FLAGS)   +.PHONY: install-scroll +install-scroll: ## Build and install the scroll-reth binary under `~/.cargo/bin`. + cargo install --path crates/scroll/bin/scroll-reth --bin scroll-reth --force --locked \ + --profile "$(PROFILE)" \ + $(CARGO_INSTALL_EXTRA_FLAGS) + .PHONY: build build: ## Build the reth binary into `target` directory. cargo build --bin reth --features "$(FEATURES)" --profile "$(PROFILE)" @@ -414,6 +420,37 @@ --tests \ --benches \ --locked \ --all-features + +lint-scroll-reth: + cargo +nightly clippy \ + --workspace \ + --bin "scroll-reth" \ + --lib \ + --examples \ + --tests \ + --benches \ + --features "$(BIN_OTHER_FEATURES)" \ + -- -D warnings + +lint-all: + cargo +nightly clippy \ + --workspace \ + --lib \ + --examples \ + --tests \ + --benches \ + --all-features \ + --locked + +lint-udeps: + cargo +nightly udeps --workspace --lib --examples --tests --benches --all-features --locked \ + --exclude reth-optimism-cli --exclude reth-optimism-consensus --exclude reth-optimism-payload-builder \ + --exclude reth-optimism-node --exclude reth-optimism-evm --exclude reth-optimism-node --exclude reth-optimism-rpc \ + --exclude op-reth --exclude "example-*" --exclude reth --exclude reth-payload-primitives \ + --exclude reth-e2e-test-utils --exclude reth-ethereum-payload-builder --exclude reth-exex-test-utils \ + --exclude reth-node-ethereum --exclude reth-scroll-cli --exclude reth-scroll-evm \ + --exclude reth-scroll-node --exclude "scroll-reth" --exclude reth-scroll-rpc \ + --exclude reth-scroll-trie   lint-typos: ensure-typos typos
diff --git reth/crates/chainspec/src/api.rs scroll-reth/crates/chainspec/src/api.rs index cb5b47bc245a7775fcd25d26648f1b5f3ce67d89..e51c2c9f07f417e46afe89bc8b485841002c1075 100644 --- reth/crates/chainspec/src/api.rs +++ scroll-reth/crates/chainspec/src/api.rs @@ -143,3 +143,13 @@ fn final_paris_total_difficulty(&self) -> Option<U256> { self.paris_block_and_final_difficulty.map(|(_, final_difficulty)| final_difficulty) } } + +/// Trait representing the current capacities of the fork. +pub trait EthereumCapabilities: EthereumHardforks { + /// Returns true if the withdrawals are active. + fn withdrawals_active(&self, ts: u64) -> bool { + self.is_shanghai_active_at_timestamp(ts) + } +} + +impl EthereumCapabilities for ChainSpec {}
diff --git reth/crates/chainspec/src/lib.rs scroll-reth/crates/chainspec/src/lib.rs index 5ba4252939925dfc2087812f5d8bca8ae5cbb4a0..6c3654a8edd01895cd52537fca346c0fd1b4a93c 100644 --- reth/crates/chainspec/src/lib.rs +++ scroll-reth/crates/chainspec/src/lib.rs @@ -25,7 +25,7 @@ pub use alloy_chains::{Chain, ChainKind, NamedChain}; /// Re-export for convenience pub use reth_ethereum_forks::*;   -pub use api::EthChainSpec; +pub use api::{EthChainSpec, EthereumCapabilities}; pub use info::ChainInfo; #[cfg(any(test, feature = "test-utils"))] pub use spec::test_fork_ids;
diff --git reth/crates/chainspec/src/spec.rs scroll-reth/crates/chainspec/src/spec.rs index 2800640b7081c11038d0c4cf0448518fe4cac1a6..7b1faccfc4d3ae2ea8c7435759f3d5ea2916200e 100644 --- reth/crates/chainspec/src/spec.rs +++ scroll-reth/crates/chainspec/src/spec.rs @@ -27,8 +27,8 @@ ChainHardforks, DisplayHardforks, EthereumHardfork, EthereumHardforks, ForkCondition, ForkFilter, ForkFilterKey, ForkHash, ForkId, Hardfork, Hardforks, Head, DEV_HARDFORKS, }; use reth_network_peers::{ - holesky_nodes, hoodi_nodes, mainnet_nodes, op_nodes, op_testnet_nodes, sepolia_nodes, - NodeRecord, + holesky_nodes, hoodi_nodes, mainnet_nodes, op_nodes, op_testnet_nodes, scroll_nodes, + scroll_sepolia_nodes, sepolia_nodes, NodeRecord, }; use reth_primitives_traits::{sync::LazyLock, SealedHeader};   @@ -623,9 +623,11 @@ C::Holesky => Some(holesky_nodes()), C::Hoodi => Some(hoodi_nodes()), // opstack uses the same bootnodes for all chains: <https://github.com/paradigmxyz/reth/issues/14603> C::Base | C::Optimism | C::Unichain | C::World => Some(op_nodes()), + C::Scroll => Some(scroll_nodes()), C::OptimismSepolia | C::BaseSepolia | C::UnichainSepolia | C::WorldSepolia => { Some(op_testnet_nodes()) } + C::ScrollSepolia => Some(scroll_sepolia_nodes()),   // fallback for optimism chains chain if chain.is_optimism() && chain.is_testnet() => Some(op_testnet_nodes()),
diff --git reth/crates/cli/commands/src/node.rs scroll-reth/crates/cli/commands/src/node.rs index 3c7b98cbe1cebcd803aedee749a8606042b623dc..7a3ac269da68a4e44b7ef530e15098b7777e70b9 100644 --- reth/crates/cli/commands/src/node.rs +++ scroll-reth/crates/cli/commands/src/node.rs @@ -236,18 +236,29 @@ assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp); }   #[test] - fn parse_common_node_command_chain_args() { + fn parse_rommon_node_command_chain_args() { for chain in SUPPORTED_CHAINS { - let args: NodeCommand<EthereumChainSpecParser> = - NodeCommand::parse_from(["reth", "--chain", chain]); + let args: NodeCommand<EthereumChainSpecParser> = NodeCommand::parse_from([ + "reth", + "--chain", + chain, + "--builder.gaslimit", + "10000000", + ]); assert_eq!(args.chain.chain, chain.parse::<reth_chainspec::Chain>().unwrap()); } }   #[test] fn parse_discovery_addr() { - let cmd: NodeCommand<EthereumChainSpecParser> = - NodeCommand::try_parse_args_from(["reth", "--discovery.addr", "127.0.0.1"]).unwrap(); + let cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::try_parse_args_from([ + "reth", + "--discovery.addr", + "127.0.0.1", + "--builder.gaslimit", + "10000000", + ]) + .unwrap(); assert_eq!(cmd.network.discovery.addr, IpAddr::V4(Ipv4Addr::LOCALHOST)); }   @@ -259,6 +270,8 @@ "--discovery.addr", "127.0.0.1", "--addr", "127.0.0.1", + "--builder.gaslimit", + "10000000", ]) .unwrap(); assert_eq!(cmd.network.discovery.addr, IpAddr::V4(Ipv4Addr::LOCALHOST)); @@ -267,46 +280,83 @@ }   #[test] fn parse_discovery_port() { - let cmd: NodeCommand<EthereumChainSpecParser> = - NodeCommand::try_parse_args_from(["reth", "--discovery.port", "300"]).unwrap(); + let cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::try_parse_args_from([ + "reth", + "--discovery.port", + "300", + "--builder.gaslimit", + "10000000", + ]) + .unwrap(); assert_eq!(cmd.network.discovery.port, 300); }   #[test] fn parse_port() { - let cmd: NodeCommand<EthereumChainSpecParser> = - NodeCommand::try_parse_args_from(["reth", "--discovery.port", "300", "--port", "99"]) - .unwrap(); + let cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::try_parse_args_from([ + "reth", + "--discovery.port", + "300", + "--port", + "99", + "--builder.gaslimit", + "10000000", + ]) + .unwrap(); assert_eq!(cmd.network.discovery.port, 300); assert_eq!(cmd.network.port, 99); }   #[test] fn parse_metrics_port() { - let cmd: NodeCommand<EthereumChainSpecParser> = - NodeCommand::try_parse_args_from(["reth", "--metrics", "9001"]).unwrap(); + let cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::try_parse_args_from([ + "reth", + "--metrics", + "9001", + "--builder.gaslimit", + "10000000", + ]) + .unwrap(); assert_eq!(cmd.metrics, Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001)));   - let cmd: NodeCommand<EthereumChainSpecParser> = - NodeCommand::try_parse_args_from(["reth", "--metrics", ":9001"]).unwrap(); + let cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::try_parse_args_from([ + "reth", + "--metrics", + ":9001", + "--builder.gaslimit", + "10000000", + ]) + .unwrap(); assert_eq!(cmd.metrics, Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001)));   - let cmd: NodeCommand<EthereumChainSpecParser> = - NodeCommand::try_parse_args_from(["reth", "--metrics", "localhost:9001"]).unwrap(); + let cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::try_parse_args_from([ + "reth", + "--metrics", + "localhost:9001", + "--builder.gaslimit", + "10000000", + ]) + .unwrap(); assert_eq!(cmd.metrics, Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001))); }   #[test] fn parse_config_path() { - let cmd: NodeCommand<EthereumChainSpecParser> = - NodeCommand::try_parse_args_from(["reth", "--config", "my/path/to/reth.toml"]).unwrap(); + let cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::try_parse_args_from([ + "reth", + "--config", + "my/path/to/reth.toml", + "--builder.gaslimit", + "10000000", + ]) + .unwrap(); // always store reth.toml in the data dir, not the chain specific data dir let data_dir = cmd.datadir.resolve_datadir(cmd.chain.chain); let config_path = cmd.config.unwrap_or_else(|| data_dir.config()); assert_eq!(config_path, Path::new("my/path/to/reth.toml"));   let cmd: NodeCommand<EthereumChainSpecParser> = - NodeCommand::try_parse_args_from(["reth"]).unwrap(); + NodeCommand::try_parse_args_from(["reth", "--builder.gaslimit", "10000000"]).unwrap();   // always store reth.toml in the data dir, not the chain specific data dir let data_dir = cmd.datadir.resolve_datadir(cmd.chain.chain); @@ -318,15 +368,21 @@ #[test] fn parse_db_path() { let cmd: NodeCommand<EthereumChainSpecParser> = - NodeCommand::try_parse_args_from(["reth"]).unwrap(); + NodeCommand::try_parse_args_from(["reth", "--builder.gaslimit", "10000000"]).unwrap(); let data_dir = cmd.datadir.resolve_datadir(cmd.chain.chain);   let db_path = data_dir.db(); let end = format!("reth/{}/db", SUPPORTED_CHAINS[0]); assert!(db_path.ends_with(end), "{:?}", cmd.config);   - let cmd: NodeCommand<EthereumChainSpecParser> = - NodeCommand::try_parse_args_from(["reth", "--datadir", "my/custom/path"]).unwrap(); + let cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::try_parse_args_from([ + "reth", + "--datadir", + "my/custom/path", + "--builder.gaslimit", + "10000000", + ]) + .unwrap(); let data_dir = cmd.datadir.resolve_datadir(cmd.chain.chain);   let db_path = data_dir.db(); @@ -335,7 +391,8 @@ }   #[test] fn parse_instance() { - let mut cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::parse_from(["reth"]); + let mut cmd: NodeCommand<EthereumChainSpecParser> = + NodeCommand::parse_from(["reth", "--builder.gaslimit", "10000000"]); cmd.rpc.adjust_instance_ports(cmd.instance); cmd.network.port = DEFAULT_DISCOVERY_PORT; // check rpc port numbers @@ -346,7 +403,7 @@ // check network listening port number assert_eq!(cmd.network.port, 30303);   let mut cmd: NodeCommand<EthereumChainSpecParser> = - NodeCommand::parse_from(["reth", "--instance", "2"]); + NodeCommand::parse_from(["reth", "--instance", "2", "--builder.gaslimit", "10000000"]); cmd.rpc.adjust_instance_ports(cmd.instance); cmd.network.port = DEFAULT_DISCOVERY_PORT + 2 - 1; // check rpc port numbers @@ -357,7 +414,7 @@ // check network listening port number assert_eq!(cmd.network.port, 30304);   let mut cmd: NodeCommand<EthereumChainSpecParser> = - NodeCommand::parse_from(["reth", "--instance", "3"]); + NodeCommand::parse_from(["reth", "--instance", "3", "--builder.gaslimit", "10000000"]); cmd.rpc.adjust_instance_ports(cmd.instance); cmd.network.port = DEFAULT_DISCOVERY_PORT + 3 - 1; // check rpc port numbers @@ -370,8 +427,12 @@ }   #[test] fn parse_with_unused_ports() { - let cmd: NodeCommand<EthereumChainSpecParser> = - NodeCommand::parse_from(["reth", "--with-unused-ports"]); + let cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::parse_from([ + "reth", + "--with-unused-ports", + "--builder.gaslimit", + "10000000", + ]); assert!(cmd.with_unused_ports); }   @@ -382,6 +443,8 @@ "reth", "--with-unused-ports", "--instance", "2", + "--builder.gaslimit", + "10000000", ]) .unwrap_err(); assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict); @@ -389,7 +452,8 @@ }   #[test] fn with_unused_ports_check_zero() { - let mut cmd: NodeCommand<EthereumChainSpecParser> = NodeCommand::parse_from(["reth"]); + let mut cmd: NodeCommand<EthereumChainSpecParser> = + NodeCommand::parse_from(["reth", "--builder.gaslimit", "10000000"]); cmd.rpc = cmd.rpc.with_unused_ports(); cmd.network = cmd.network.with_unused_ports();
diff --git reth/crates/cli/commands/src/stage/dump/merkle.rs scroll-reth/crates/cli/commands/src/stage/dump/merkle.rs index 2c0e784006feb187ea4540c2b9906cd0217e8432..cc21c0fc29f49d03cd71e5b321d0af4cc1cfc5aa 100644 --- reth/crates/cli/commands/src/stage/dump/merkle.rs +++ scroll-reth/crates/cli/commands/src/stage/dump/merkle.rs @@ -4,7 +4,7 @@ use super::setup; use alloy_primitives::BlockNumber; use eyre::Result; use reth_config::config::EtlConfig; -use reth_consensus::{ConsensusError, FullConsensus}; +use reth_consensus::{noop::NoopConsensus, ConsensusError, FullConsensus}; use reth_db::DatabaseEnv; use reth_db_api::{database::Database, table::TableImporter, tables}; use reth_db_common::DbTool; @@ -96,7 +96,7 @@ StorageHashingStage::default().unwind(&provider, unwind).unwrap(); AccountHashingStage::default().unwind(&provider, unwind).unwrap();   - MerkleStage::default_unwind().unwind(&provider, unwind)?; + MerkleStage::<N::Primitives>::new_unwind(NoopConsensus::arc()).unwind(&provider, unwind)?;   // Bring Plainstate to TO (hashing stage execution requires it) let mut exec_stage = ExecutionStage::new( @@ -159,10 +159,11 @@ { info!(target: "reth::cli", "Executing stage."); let provider = output_provider_factory.database_provider_rw()?;   - let mut stage = MerkleStage::Execution { + let mut stage = MerkleStage::<N::Primitives>::Execution { // Forces updating the root instead of calculating from scratch rebuild_threshold: u64::MAX, incremental_threshold: u64::MAX, + consensus: NoopConsensus::arc(), };   loop {
diff --git reth/crates/cli/commands/src/stage/run.rs scroll-reth/crates/cli/commands/src/stage/run.rs index f608a18fff472f260dbf60474ac18e8434a41688..bba9858f21239f3186bd08334a555c3275cc820c 100644 --- reth/crates/cli/commands/src/stage/run.rs +++ scroll-reth/crates/cli/commands/src/stage/run.rs @@ -296,13 +296,17 @@ etl_config, )), None, ), - StageEnum::Merkle => ( - Box::new(MerkleStage::new_execution( - config.stages.merkle.rebuild_threshold, - config.stages.merkle.incremental_threshold, - )), - Some(Box::new(MerkleStage::default_unwind())), - ), + StageEnum::Merkle => { + let consensus = Arc::new(components.consensus().clone()); + ( + Box::new(MerkleStage::<N::Primitives>::new_execution( + config.stages.merkle.rebuild_threshold, + config.stages.merkle.incremental_threshold, + consensus.clone(), + )), + Some(Box::new(MerkleStage::<N::Primitives>::new_unwind(consensus))), + ) + } StageEnum::AccountHistory => ( Box::new(IndexAccountHistoryStage::new( config.stages.index_account_history,
diff --git reth/crates/consensus/consensus/src/lib.rs scroll-reth/crates/consensus/consensus/src/lib.rs index 93babfe3a14635698fd877fb080052f52d91a88c..951c2c0ef5566c0047cf0b0740447888229a0b4b 100644 --- reth/crates/consensus/consensus/src/lib.rs +++ scroll-reth/crates/consensus/consensus/src/lib.rs @@ -18,7 +18,7 @@ use reth_execution_types::BlockExecutionResult; use reth_primitives_traits::{ constants::{MAXIMUM_GAS_LIMIT_BLOCK, MINIMUM_GAS_LIMIT}, transaction::error::InvalidTransactionError, - Block, GotExpected, GotExpectedBoxed, NodePrimitives, RecoveredBlock, SealedBlock, + Block, BlockHeader, GotExpected, GotExpectedBoxed, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader, };   @@ -73,7 +73,7 @@ }   /// `HeaderValidator` is a protocol that validates headers and their relationships. #[auto_impl::auto_impl(&, Arc)] -pub trait HeaderValidator<H = Header>: Debug + Send + Sync { +pub trait HeaderValidator<H: BlockHeader = Header>: Debug + Send + Sync { /// Validate if header is correct and follows consensus specification. /// /// This is called on standalone header to check if all hashes are correct. @@ -121,6 +121,22 @@ } } Ok(()) } + + /// Validate the block header against a provided expected state root. + fn validate_state_root(&self, header: &H, root: B256) -> Result<(), ConsensusError> { + validate_state_root(header, root) + } +} + +/// Validate the provided state root against the block's state root. +pub fn validate_state_root<H: BlockHeader>(header: &H, root: B256) -> Result<(), ConsensusError> { + if header.state_root() != root { + return Err(ConsensusError::BodyStateRootDiff( + GotExpected { got: root, expected: header.state_root() }.into(), + )) + } + + Ok(()) }   /// Consensus Errors
diff --git reth/crates/consensus/consensus/src/noop.rs scroll-reth/crates/consensus/consensus/src/noop.rs index 259fae27d677eb5d57a1da8030a88b5b0206ff9e..dd0f9ae1e30eccfb1287bd1e5883a91bd9a6c6e0 100644 --- reth/crates/consensus/consensus/src/noop.rs +++ scroll-reth/crates/consensus/consensus/src/noop.rs @@ -1,7 +1,10 @@ use crate::{Consensus, ConsensusError, FullConsensus, HeaderValidator}; use alloc::sync::Arc; +use alloy_primitives::B256; use reth_execution_types::BlockExecutionResult; -use reth_primitives_traits::{Block, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader}; +use reth_primitives_traits::{ + Block, BlockHeader, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader, +};   /// A Consensus implementation that does nothing. #[derive(Debug, Copy, Clone, Default)] @@ -15,7 +18,7 @@ Arc::new(Self::default()) } }   -impl<H> HeaderValidator<H> for NoopConsensus { +impl<H: BlockHeader> HeaderValidator<H> for NoopConsensus { fn validate_header(&self, _header: &SealedHeader<H>) -> Result<(), ConsensusError> { Ok(()) } @@ -25,6 +28,10 @@ &self, _header: &SealedHeader<H>, _parent: &SealedHeader<H>, ) -> Result<(), ConsensusError> { + Ok(()) + } + + fn validate_state_root(&self, _header: &H, _root: B256) -> Result<(), ConsensusError> { Ok(()) } }
diff --git reth/crates/consensus/consensus/src/test_utils.rs scroll-reth/crates/consensus/consensus/src/test_utils.rs index ad881cc9a7fb0c6b32c9543ec2e72f068b9b034a..e36ab0141761a4ef5e6a335c2710830580bea5eb 100644 --- reth/crates/consensus/consensus/src/test_utils.rs +++ scroll-reth/crates/consensus/consensus/src/test_utils.rs @@ -1,7 +1,10 @@ use crate::{Consensus, ConsensusError, FullConsensus, HeaderValidator}; +use alloy_primitives::B256; use core::sync::atomic::{AtomicBool, Ordering}; use reth_execution_types::BlockExecutionResult; -use reth_primitives_traits::{Block, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader}; +use reth_primitives_traits::{ + Block, BlockHeader, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader, +};   /// Consensus engine implementation for testing #[derive(Debug)] @@ -84,7 +87,7 @@ } } }   -impl<H> HeaderValidator<H> for TestConsensus { +impl<H: BlockHeader> HeaderValidator<H> for TestConsensus { fn validate_header(&self, _header: &SealedHeader<H>) -> Result<(), ConsensusError> { if self.fail_validation() { Err(ConsensusError::BaseFeeMissing) @@ -103,5 +106,9 @@ Err(ConsensusError::BaseFeeMissing) } else { Ok(()) } + } + + fn validate_state_root(&self, _header: &H, _root: B256) -> Result<(), ConsensusError> { + Ok(()) } }
diff --git reth/crates/e2e-test-utils/src/lib.rs scroll-reth/crates/e2e-test-utils/src/lib.rs index 0037197f261654fa3ca18be536e78cbbbed0f7f6..2fd5631dfb208203bb4a092f6868bdecbf46492d 100644 --- reth/crates/e2e-test-utils/src/lib.rs +++ scroll-reth/crates/e2e-test-utils/src/lib.rs @@ -5,9 +5,10 @@ use reth_chainspec::{ChainSpec, EthChainSpec}; use reth_db::{test_utils::TempDatabase, DatabaseEnv}; use reth_engine_local::LocalPayloadAttributesBuilder; use reth_network_api::test_utils::PeersHandleProvider; +use reth_node_api::NodeAddOns; use reth_node_builder::{ components::NodeComponentsBuilder, - rpc::{EngineValidatorAddOn, RethRpcAddOns}, + rpc::{EngineValidatorAddOn, RethRpcAddOns, RpcHandleProvider}, EngineNodeLauncher, FullNodeTypesAdapter, Node, NodeAdapter, NodeBuilder, NodeComponents, NodeConfig, NodeHandle, NodePrimitives, NodeTypes, NodeTypesWithDBAdapter, PayloadAttributesBuilder, PayloadTypes, @@ -61,6 +62,7 @@ >, N::AddOns: RethRpcAddOns<Adapter<N>> + EngineValidatorAddOn<Adapter<N>>, LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder<<<N as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes>, + TmpNodeAddOnsHandle<N>: RpcHandleProvider<Adapter<N>, TmpNodeEthApi<N>>, { let tasks = TaskManager::current(); let exec = tasks.executor(); @@ -124,6 +126,7 @@ where N: NodeBuilderHelper, LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder<<N::Payload as PayloadTypes>::PayloadAttributes>, + TmpNodeAddOnsHandle<N>: RpcHandleProvider<Adapter<N>, TmpNodeEthApi<N>>, { setup_engine_with_connection::<N>( num_nodes, @@ -153,6 +156,7 @@ where N: NodeBuilderHelper, LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder<<N::Payload as PayloadTypes>::PayloadAttributes>, + TmpNodeAddOnsHandle<N>: RpcHandleProvider<Adapter<N>, TmpNodeEthApi<N>>, { let tasks = TaskManager::current(); let exec = tasks.executor(); @@ -235,6 +239,14 @@ TmpNodeAdapter<N, Provider>, >>::Components, >;   +/// Type alias for a `NodeHandle` for a `TmpNodeAdapter`. +pub type TmpNodeAddOnsHandle<N> = + <<N as Node<TmpNodeAdapter<N>>>::AddOns as NodeAddOns<Adapter<N>>>::Handle; + +/// Type alias for the `EthApi` for a `TmpNodeAdapter`. +pub type TmpNodeEthApi<N> = + <<N as Node<TmpNodeAdapter<N>>>::AddOns as RethRpcAddOns<Adapter<N>>>::EthApi; + /// Type alias for a type of `NodeHelper` pub type NodeHelperType<N, Provider = BlockchainProvider<NodeTypesWithDBAdapter<N, TmpDB>>> = NodeTestContext<Adapter<N, Provider>, <N as Node<TmpNodeAdapter<N, Provider>>>::AddOns>; @@ -271,6 +283,7 @@ ChainSpec: From<ChainSpec> + Clone, >, LocalPayloadAttributesBuilder<Self::ChainSpec>: PayloadAttributesBuilder<<Self::Payload as PayloadTypes>::PayloadAttributes>, + TmpNodeAddOnsHandle<Self>: RpcHandleProvider<Adapter<Self>, TmpNodeEthApi<Self>>, { }   @@ -305,5 +318,6 @@ ChainSpec: From<ChainSpec> + Clone, >, LocalPayloadAttributesBuilder<Self::ChainSpec>: PayloadAttributesBuilder<<Self::Payload as PayloadTypes>::PayloadAttributes>, + TmpNodeAddOnsHandle<Self>: RpcHandleProvider<Adapter<Self>, TmpNodeEthApi<Self>>, { }
diff --git reth/crates/e2e-test-utils/src/node.rs scroll-reth/crates/e2e-test-utils/src/node.rs index 080304ca0c80a1e75c5bf219e39bc4f44fa96a3d..6572223f225730125354f8c54b95f23ce9d98a10 100644 --- reth/crates/e2e-test-utils/src/node.rs +++ scroll-reth/crates/e2e-test-utils/src/node.rs @@ -13,7 +13,10 @@ use reth_node_api::{ Block, BlockBody, BlockTy, EngineApiMessageVersion, FullNodeComponents, PayloadTypes, PrimitivesTy, }; -use reth_node_builder::{rpc::RethRpcAddOns, FullNode, NodeTypes}; +use reth_node_builder::{ + rpc::{RethRpcAddOns, RpcHandleProvider}, + FullNode, NodeTypes, +}; use reth_node_core::primitives::SignedTransaction; use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; use reth_provider::{ @@ -33,6 +36,7 @@ pub struct NodeTestContext<Node, AddOns> where Node: FullNodeComponents, AddOns: RethRpcAddOns<Node>, + AddOns::Handle: RpcHandleProvider<Node, <AddOns as RethRpcAddOns<Node>>::EthApi>, { /// The core structure representing the full node. pub inner: FullNode<Node, AddOns>, @@ -53,6 +57,7 @@ Node: FullNodeComponents, Node::Types: NodeTypes<ChainSpec: EthereumHardforks, Payload = Payload>, Node::Network: PeersHandleProvider, AddOns: RethRpcAddOns<Node>, + AddOns::Handle: RpcHandleProvider<Node, <AddOns as RethRpcAddOns<Node>>::EthApi>, { /// Creates a new test node pub async fn new( @@ -67,7 +72,7 @@ attributes_generator, ) .await?, network: NetworkTestContext::new(node.network.clone()), - rpc: RpcTestContext { inner: node.add_ons_handle.rpc_registry }, + rpc: RpcTestContext { inner: node.add_ons_handle.rpc_handle().rpc_registry.clone() }, canonical_stream: node.provider.canonical_state_stream(), }) } @@ -258,6 +263,7 @@ /// Sends a forkchoice update message to the engine. pub async fn update_forkchoice(&self, current_head: B256, new_head: B256) -> eyre::Result<()> { self.inner .add_ons_handle + .rpc_handle() .beacon_engine_handle .fork_choice_updated( ForkchoiceState { @@ -283,6 +289,7 @@ pub async fn submit_payload(&self, payload: Payload::BuiltPayload) -> eyre::Result<B256> { let block_hash = payload.block().hash(); self.inner .add_ons_handle + .rpc_handle() .beacon_engine_handle .new_payload(Payload::block_to_payload(payload.block().clone())) .await?;
diff --git reth/crates/e2e-test-utils/src/testsuite/mod.rs scroll-reth/crates/e2e-test-utils/src/testsuite/mod.rs index b172a8d0e9fb575011ee8f3c7390855d78a6627e..84c9d126bd5d2f98c47da0d80403e925a797268b 100644 --- reth/crates/e2e-test-utils/src/testsuite/mod.rs +++ scroll-reth/crates/e2e-test-utils/src/testsuite/mod.rs @@ -2,7 +2,8 @@ //! Utilities for running e2e tests against a node or a network of nodes.   use crate::{ testsuite::actions::{Action, ActionBox}, - NodeBuilderHelper, PayloadAttributesBuilder, + Adapter, NodeBuilderHelper, PayloadAttributesBuilder, RpcHandleProvider, TmpNodeAddOnsHandle, + TmpNodeEthApi, }; use alloy_primitives::B256; use eyre::Result; @@ -327,6 +328,7 @@ N: NodeBuilderHelper, LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder< <<N as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes, >, + TmpNodeAddOnsHandle<N>: RpcHandleProvider<Adapter<N>, TmpNodeEthApi<N>>, { let mut setup = self.setup.take();
diff --git reth/crates/e2e-test-utils/src/testsuite/setup.rs scroll-reth/crates/e2e-test-utils/src/testsuite/setup.rs index 2db463cfd84f1a7cd570444b9aade3fe8f5ab7da..18f270aca1c10038abf9ec43876a1dd88fc5ae16 100644 --- reth/crates/e2e-test-utils/src/testsuite/setup.rs +++ scroll-reth/crates/e2e-test-utils/src/testsuite/setup.rs @@ -1,8 +1,8 @@ //! Test setup utilities for configuring the initial state.   use crate::{ - setup_engine_with_connection, testsuite::Environment, NodeBuilderHelper, - PayloadAttributesBuilder, + setup_engine_with_connection, testsuite::Environment, Adapter, NodeBuilderHelper, + PayloadAttributesBuilder, RpcHandleProvider, TmpNodeAddOnsHandle, TmpNodeEthApi, }; use alloy_eips::BlockNumberOrTag; use alloy_primitives::B256; @@ -146,6 +146,7 @@ N: NodeBuilderHelper, LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder< <<N as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes, >, + TmpNodeAddOnsHandle<N>: RpcHandleProvider<Adapter<N>, TmpNodeEthApi<N>>, { // Create nodes with imported chain data let import_result = self.create_nodes_with_import::<N>(rlp_path).await?; @@ -177,6 +178,7 @@ N: NodeBuilderHelper, LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder< <<N as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes, >, + TmpNodeAddOnsHandle<N>: RpcHandleProvider<Adapter<N>, TmpNodeEthApi<N>>, { // If import_rlp_path is set, use apply_with_import instead if let Some(rlp_path) = self.import_rlp_path.take() { @@ -251,6 +253,7 @@ N: NodeBuilderHelper, LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder< <<N as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes, >, + TmpNodeAddOnsHandle<N>: RpcHandleProvider<Adapter<N>, TmpNodeEthApi<N>>, { let chain_spec = self.chain_spec.clone().ok_or_else(|| eyre!("Chain specification is required"))?; @@ -286,6 +289,7 @@ N: NodeBuilderHelper, LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder< <<N as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes, >, + TmpNodeAddOnsHandle<N>: RpcHandleProvider<Adapter<N>, TmpNodeEthApi<N>>, { move |timestamp| { let attributes = PayloadAttributes {
diff --git reth/crates/e2e-test-utils/src/transaction.rs scroll-reth/crates/e2e-test-utils/src/transaction.rs index 54f984692423783cec0903ec2c26af5918e3da9b..3ee437ce376e58befef9bf7150ca9f14252fd62d 100644 --- reth/crates/e2e-test-utils/src/transaction.rs +++ scroll-reth/crates/e2e-test-utils/src/transaction.rs @@ -36,6 +36,17 @@ let signed = Self::transfer_tx(chain_id, wallet).await; signed.encoded_2718().into() }   + /// Creates a transfer with a nonce and signs it, returning bytes. + pub async fn transfer_tx_nonce_bytes( + chain_id: u64, + wallet: PrivateKeySigner, + nonce: u64, + ) -> Bytes { + let tx = tx(chain_id, 21000, None, None, nonce, Some(20e9 as u128)); + let signed = Self::sign_tx(wallet, tx).await; + signed.encoded_2718().into() + } + /// Creates a deployment transaction and signs it, returning an envelope. pub async fn deploy_tx( chain_id: u64,
diff --git reth/crates/engine/local/Cargo.toml scroll-reth/crates/engine/local/Cargo.toml index 98793a24b21d53d32f3545147b881ad5a9f4e21d..2de5ec3c8826b0c41324a0f2ae497ab05fd76cd4 100644 --- reth/crates/engine/local/Cargo.toml +++ scroll-reth/crates/engine/local/Cargo.toml @@ -23,6 +23,9 @@ alloy-consensus.workspace = true alloy-primitives = { workspace = true, features = ["getrandom"] } alloy-rpc-types-engine.workspace = true   +# scroll +scroll-alloy-rpc-types-engine = { workspace = true, optional = true } + # async tokio.workspace = true tokio-stream.workspace = true @@ -44,3 +47,7 @@ "dep:op-alloy-rpc-types-engine", "dep:reth-optimism-chainspec", "reth-payload-primitives/op", ] +scroll-alloy-traits = [ + "dep:scroll-alloy-rpc-types-engine", + "reth-payload-primitives/scroll-alloy-traits", +]
diff --git reth/crates/engine/local/src/payload.rs scroll-reth/crates/engine/local/src/payload.rs index 408ea2b8d059f2169e8ec66ec85e0c01641d9aca..eb9a3370aeb0d88baa34b728ee1169b944522d02 100644 --- reth/crates/engine/local/src/payload.rs +++ scroll-reth/crates/engine/local/src/payload.rs @@ -64,3 +64,20 @@ eip_1559_params: None, } } } + +#[cfg(feature = "scroll-alloy-traits")] +impl<ChainSpec> PayloadAttributesBuilder<scroll_alloy_rpc_types_engine::ScrollPayloadAttributes> + for LocalPayloadAttributesBuilder<ChainSpec> +where + ChainSpec: Send + Sync + EthereumHardforks + 'static, +{ + fn build(&self, timestamp: u64) -> scroll_alloy_rpc_types_engine::ScrollPayloadAttributes { + scroll_alloy_rpc_types_engine::ScrollPayloadAttributes { + payload_attributes: self.build(timestamp), + transactions: None, + no_tx_pool: false, + block_data_hint: scroll_alloy_rpc_types_engine::BlockDataHint::none(), + gas_limit: None, + } + } +}
diff --git reth/crates/engine/tree/benches/state_root_task.rs scroll-reth/crates/engine/tree/benches/state_root_task.rs index 6539ee9d9a4a21261641239caff56d4362762408..e00d7ba128fbd82eaeb7fbe23a850fc4fe378dd1 100644 --- reth/crates/engine/tree/benches/state_root_task.rs +++ scroll-reth/crates/engine/tree/benches/state_root_task.rs @@ -70,11 +70,13 @@ transaction_id: 0, } } else { RevmAccount { + #[allow(clippy::needless_update)] info: AccountInfo { balance: U256::from(rng.random::<u64>()), nonce: rng.random::<u64>(), code_hash: KECCAK_EMPTY, code: Some(Default::default()), + ..Default::default() }, storage: (0..rng.random_range(0..=params.storage_slots_per_account)) .map(|_| {
diff --git reth/crates/engine/tree/src/tree/payload_validator.rs scroll-reth/crates/engine/tree/src/tree/payload_validator.rs index ce55c775d06b91c8737fa16d347f8c8296efdcdc..ee0bffe02dde3846ba3a9780ca921c127862a1c6 100644 --- reth/crates/engine/tree/src/tree/payload_validator.rs +++ scroll-reth/crates/engine/tree/src/tree/payload_validator.rs @@ -19,7 +19,7 @@ use alloy_primitives::B256; use reth_chain_state::{ CanonicalInMemoryState, ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates, }; -use reth_consensus::{ConsensusError, FullConsensus}; +use reth_consensus::{ConsensusError, FullConsensus, HeaderValidator}; use reth_engine_primitives::{ ConfigureEngineEvm, ExecutableTxIterator, ExecutionPayload, InvalidBlockHook, PayloadValidator, }; @@ -499,7 +499,7 @@ Ok(StateRootComputeOutcome { state_root, trie_updates }) => { let elapsed = execution_finish.elapsed(); info!(target: "engine::tree", ?state_root, ?elapsed, "State root task finished"); // we double check the state root here for good measure - if state_root == block.header().state_root() { + if self.consensus.validate_state_root(block.header(), state_root).is_ok() { maybe_state_root = Some((state_root, trie_updates, elapsed)) } else { warn!( @@ -567,7 +567,7 @@ self.metrics.block_validation.record_state_root(&trie_output, root_elapsed.as_secs_f64()); debug!(target: "engine::tree", ?root_elapsed, block=?block_num_hash, "Calculated state root");   // ensure state root matches - if state_root != block.header().state_root() { + if self.consensus.validate_state_root(block.header(), state_root).is_err() { // call post-block hook self.on_invalid_block( &parent_block,
diff --git reth/crates/evm/evm/Cargo.toml scroll-reth/crates/evm/evm/Cargo.toml index b7515ccc4085c7c5d30858d24675e48e1baea09f..9733da4f360d445ed032c2a061adc46f6da31f37 100644 --- reth/crates/evm/evm/Cargo.toml +++ scroll-reth/crates/evm/evm/Cargo.toml @@ -28,6 +28,9 @@ alloy-eips.workspace = true alloy-evm.workspace = true alloy-consensus.workspace = true   +# scroll +scroll-alloy-evm = { workspace = true, optional = true, default-features = false } + auto_impl.workspace = true derive_more.workspace = true futures-util.workspace = true @@ -56,6 +59,7 @@ "derive_more/std", "reth-storage-api/std", "reth-trie-common/std", "reth-ethereum-primitives/std", + "scroll-alloy-evm?/std", ] metrics = ["std", "dep:metrics", "dep:reth-metrics"] test-utils = [ @@ -64,3 +68,4 @@ "reth-trie-common/test-utils", "reth-ethereum-primitives/test-utils", ] op = ["alloy-evm/op", "reth-primitives-traits/op"] +scroll-alloy-traits = ["dep:scroll-alloy-evm", "reth-primitives-traits/scroll-alloy-traits"]
diff --git reth/crates/evm/evm/src/either.rs scroll-reth/crates/evm/evm/src/either.rs index 904ce7ebbd6dfab016a329df9e2616fc63b12bfc..d497e142d7a50cc29d84b0a04f368f35a7f6c442 100644 --- reth/crates/evm/evm/src/either.rs +++ scroll-reth/crates/evm/evm/src/either.rs @@ -58,7 +58,7 @@ block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>, state: F, ) -> Result<BlockExecutionOutput<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error> where - F: FnMut(&revm::database::State<DB>), + F: FnMut(&mut revm::database::State<DB>), { match self { Self::Left(a) => a.execute_with_state_closure(block, state),
diff --git reth/crates/evm/evm/src/execute.rs scroll-reth/crates/evm/evm/src/execute.rs index 93ced85fabe395ab89e801df7cae277a639fa2aa..82e9ae2d562500c3a4855aeb8bdcc945e8f413c7 100644 --- reth/crates/evm/evm/src/execute.rs +++ scroll-reth/crates/evm/evm/src/execute.rs @@ -100,11 +100,11 @@ block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>, mut f: F, ) -> Result<BlockExecutionOutput<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error> where - F: FnMut(&State<DB>), + F: FnMut(&mut State<DB>), { let result = self.execute_one(block)?; let mut state = self.into_state(); - f(&state); + f(&mut state); Ok(BlockExecutionOutput { state: state.take_bundle(), result }) }   @@ -593,7 +593,6 @@ #[cfg(test)] mod tests { use super::*; use crate::Address; - use alloy_consensus::constants::KECCAK_EMPTY; use alloy_evm::block::state_changes::balance_increment_state; use alloy_primitives::{address, map::HashMap, U256}; use core::marker::PhantomData; @@ -665,12 +664,8 @@ ) -> State<CacheDB<EmptyDB>> { let db = CacheDB::<EmptyDB>::default(); let mut state = State::builder().with_database(db).with_bundle_update().build();   - let account_info = AccountInfo { - balance: U256::from(balance), - nonce, - code_hash: KECCAK_EMPTY, - code: None, - }; + let account_info = + AccountInfo { balance: U256::from(balance), nonce, ..Default::default() }; state.insert_account(addr, account_info); state } @@ -706,8 +701,7 @@ let addr2 = address!("0x2000000000000000000000000000000000000000");   let mut state = setup_state_with_account(addr1, 100, 1);   - let account2 = - AccountInfo { balance: U256::from(200), nonce: 1, code_hash: KECCAK_EMPTY, code: None }; + let account2 = AccountInfo { balance: U256::from(200), nonce: 1, ..Default::default() }; state.insert_account(addr2, account2);   let mut increments = HashMap::default(); @@ -728,8 +722,7 @@ let addr2 = address!("0x2000000000000000000000000000000000000000");   let mut state = setup_state_with_account(addr1, 100, 1);   - let account2 = - AccountInfo { balance: U256::from(200), nonce: 1, code_hash: KECCAK_EMPTY, code: None }; + let account2 = AccountInfo { balance: U256::from(200), nonce: 1, ..Default::default() }; state.insert_account(addr2, account2);   let mut increments = HashMap::default();
diff --git reth/crates/evm/evm/src/lib.rs scroll-reth/crates/evm/evm/src/lib.rs index dc47fecb9c22be84660769ccb6cf74e38ffcab24..412abb5db1da3b1364baf7cd4056889a8fbc0932 100644 --- reth/crates/evm/evm/src/lib.rs +++ scroll-reth/crates/evm/evm/src/lib.rs @@ -574,3 +574,22 @@ fn set_access_list(&mut self, access_list: AccessList) { self.base.set_access_list(access_list); } } + +#[cfg(feature = "scroll-alloy-traits")] +impl<T: TransactionEnv> TransactionEnv for scroll_alloy_evm::ScrollTransactionIntoTxEnv<T> { + fn set_gas_limit(&mut self, gas_limit: u64) { + self.base.set_gas_limit(gas_limit); + } + + fn nonce(&self) -> u64 { + TransactionEnv::nonce(&self.base) + } + + fn set_nonce(&mut self, nonce: u64) { + self.base.set_nonce(nonce); + } + + fn set_access_list(&mut self, access_list: AccessList) { + self.base.set_access_list(access_list); + } +}
diff --git reth/crates/evm/evm/src/metrics.rs scroll-reth/crates/evm/evm/src/metrics.rs index a80b0cb0692d8e32f79b4be1d366f85537f98f88..eaa376ec31107ad6016f2d496f9ec727ad47cf1f 100644 --- reth/crates/evm/evm/src/metrics.rs +++ scroll-reth/crates/evm/evm/src/metrics.rs @@ -162,6 +162,7 @@ }   #[cfg(test)] mod tests { + #![allow(clippy::needless_update)] use super::*; use alloy_eips::eip7685::Requests; use alloy_evm::{block::CommitChanges, EthEvm}; @@ -291,6 +292,7 @@ balance: U256::from(100), nonce: 10, code_hash: B256::random(), code: Default::default(), + ..Default::default() }, storage, status: AccountStatus::default(),
diff --git reth/crates/exex/types/src/notification.rs scroll-reth/crates/exex/types/src/notification.rs index cf0d7580556a8ec2d9f947f4e35c0731b2eaafc3..8a62fa9230e86d0df64bf133510a6260fcbb87e3 100644 --- reth/crates/exex/types/src/notification.rs +++ scroll-reth/crates/exex/types/src/notification.rs @@ -7,6 +7,7 @@ /// Notifications sent to an `ExEx`. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[allow(clippy::large_enum_variant)] pub enum ExExNotification<N: NodePrimitives = reth_chain_state::EthPrimitives> { /// Chain got committed without a reorg, and only the new chain is returned. ChainCommitted {
diff --git reth/crates/net/eth-wire-types/src/capability.rs scroll-reth/crates/net/eth-wire-types/src/capability.rs index 3f39bed60647df467d8d7cccffb927d429159fb9..2c8119027d1b9bf75eaefe819a1c899792c3e77b 100644 --- reth/crates/net/eth-wire-types/src/capability.rs +++ scroll-reth/crates/net/eth-wire-types/src/capability.rs @@ -160,6 +160,7 @@ impl Capabilities { /// Returns all capabilities. #[inline] + #[allow(clippy::missing_const_for_fn)] pub fn capabilities(&self) -> &[Capability] { &self.inner }
diff --git reth/crates/net/network-api/Cargo.toml scroll-reth/crates/net/network-api/Cargo.toml index b0ebed8bcfba346b4cc2cb807f35b3cfb881c064..2bb54aa0d6b1886b3c167f916637bba57433ec4a 100644 --- reth/crates/net/network-api/Cargo.toml +++ scroll-reth/crates/net/network-api/Cargo.toml @@ -19,6 +19,7 @@ reth-network-p2p.workspace = true reth-eth-wire-types.workspace = true reth-tokio-util.workspace = true reth-ethereum-forks.workspace = true +reth-primitives-traits.workspace = true   # ethereum alloy-consensus.workspace = true @@ -48,4 +49,5 @@ "enr/serde", "reth-ethereum-forks/serde", "alloy-consensus/serde", "alloy-rpc-types-eth/serde", + "reth-primitives-traits/serde", ]
diff --git reth/crates/net/network-api/src/block.rs scroll-reth/crates/net/network-api/src/block.rs new file mode 100644 index 0000000000000000000000000000000000000000..994c90a09a847e0d6f879408bfb972b7afc0a965 --- /dev/null +++ scroll-reth/crates/net/network-api/src/block.rs @@ -0,0 +1,26 @@ +use super::*; +use alloy_primitives::B256; +use reth_tokio_util::EventStream; +use tokio::sync::oneshot; + +/// The message that is broadcast to subscribers of the block import channel. +#[derive(Debug, Clone)] +pub struct NewBlockWithPeer<B> { + /// The peer that sent the block. + pub peer_id: PeerId, + /// The block that was received. + pub block: B, +} + +/// Provides a listener for new blocks on the eth wire protocol. +pub trait EthWireProvider<N: NetworkPrimitives> { + /// Create a new eth wire block listener. + fn eth_wire_block_listener( + &self, + ) -> impl Future< + Output = Result<EventStream<NewBlockWithPeer<N::Block>>, oneshot::error::RecvError>, + > + Send; + + /// Announce a new block to the network over the eth wire protocol. + fn eth_wire_announce_block(&self, block: N::NewBlockPayload, hash: B256); +}
diff --git reth/crates/net/network-api/src/lib.rs scroll-reth/crates/net/network-api/src/lib.rs index 58fe2c124e86a70b521d933b829fdf14ff14a96b..620592692a8a7c606e81fade900506999bd395a1 100644 --- reth/crates/net/network-api/src/lib.rs +++ scroll-reth/crates/net/network-api/src/lib.rs @@ -20,7 +20,11 @@ pub mod events; /// Implementation of network traits for that does nothing. pub mod noop;   +/// Type used for the eth wire bridge. +pub mod block; + pub mod test_utils; +use block::EthWireProvider; use test_utils::PeersHandleProvider;   pub use alloy_rpc_types_admin::EthProtocolInfo; @@ -54,6 +58,7 @@ + NetworkInfo + NetworkEventListenerProvider + Peers + PeersHandleProvider + + EthWireProvider<Self::Primitives> + Clone + Unpin + 'static @@ -68,6 +73,7 @@ + NetworkInfo + NetworkEventListenerProvider + Peers + PeersHandleProvider + + EthWireProvider<Self::Primitives> + Clone + Unpin + 'static
diff --git reth/crates/net/network-api/src/noop.rs scroll-reth/crates/net/network-api/src/noop.rs index c650db0afc448ea61ca0f0f0af58ae5d5d733f66..6625029db85900bbcece3c76ab499d5ac0aa7159 100644 --- reth/crates/net/network-api/src/noop.rs +++ scroll-reth/crates/net/network-api/src/noop.rs @@ -7,6 +7,7 @@ use core::{fmt, marker::PhantomData}; use std::net::{IpAddr, SocketAddr};   use crate::{ + block::{EthWireProvider, NewBlockWithPeer}, events::{NetworkPeersEvents, PeerEventStream}, test_utils::{PeersHandle, PeersHandleProvider}, BlockDownloaderProvider, DiscoveryEvent, NetworkError, NetworkEvent, @@ -22,7 +23,7 @@ use reth_network_p2p::{sync::NetworkSyncUpdater, NoopFullBlockClient}; use reth_network_peers::NodeRecord; use reth_network_types::{PeerKind, Reputation, ReputationChangeKind}; use reth_tokio_util::{EventSender, EventStream}; -use tokio::sync::{mpsc, oneshot}; +use tokio::sync::{mpsc, oneshot, oneshot::error::RecvError}; use tokio_stream::wrappers::UnboundedReceiverStream;   /// A type that implements all network trait that does nothing. @@ -197,6 +198,22 @@ fn discovery_listener(&self) -> UnboundedReceiverStream<DiscoveryEvent> { let (_, rx) = mpsc::unbounded_channel(); UnboundedReceiverStream::new(rx) + } +} + +impl<N: NetworkPrimitives> EthWireProvider<N> for NoopNetwork<N> { + async fn eth_wire_block_listener( + &self, + ) -> Result<EventStream<NewBlockWithPeer<N::Block>>, RecvError> { + unreachable!() + } + + fn eth_wire_announce_block( + &self, + _block: <N as NetworkPrimitives>::NewBlockPayload, + _hash: alloy_primitives::B256, + ) { + unreachable!() } }
diff --git reth/crates/net/network/src/config.rs scroll-reth/crates/net/network/src/config.rs index cb04541a0200330e1d398fb79eea73a2ab6b0ad3..aee5dce37fb9138994ab26d9c532b886e3f33c0c 100644 --- reth/crates/net/network/src/config.rs +++ scroll-reth/crates/net/network/src/config.rs @@ -4,6 +4,7 @@ use crate::{ error::NetworkError, import::{BlockImport, ProofOfStakeBlockImport}, transactions::TransactionsManagerConfig, + transform::header::HeaderTransform, NetworkHandle, NetworkManager, }; use reth_chainspec::{ChainSpecProvider, EthChainSpec, Hardforks}; @@ -90,6 +91,8 @@ /// <https://github.com/ethereum/devp2p/blob/master/rlpx.md#initial-handshake>. /// This can be overridden to support custom handshake logic via the /// [`NetworkConfigBuilder`]. pub handshake: Arc<dyn EthRlpxHandshake>, + /// A transformation hook applied to the downloaded headers. + pub header_transform: Box<dyn HeaderTransform<N::BlockHeader>>, }   // === impl NetworkConfig === @@ -217,6 +220,8 @@ nat: Option<NatResolver>, /// The Ethereum P2P handshake, see also: /// <https://github.com/ethereum/devp2p/blob/master/rlpx.md#initial-handshake>. handshake: Arc<dyn EthRlpxHandshake>, + /// The header transform type. + header_transform: Option<Box<dyn HeaderTransform<N::BlockHeader>>>, }   impl NetworkConfigBuilder<EthNetworkPrimitives> { @@ -257,6 +262,7 @@ block_import: None, transactions_manager_config: Default::default(), nat: None, handshake: Arc::new(EthHandshake::default()), + header_transform: None, } }   @@ -565,6 +571,15 @@ self.handshake = handshake; self }   + /// Sets the header transform type. + pub fn header_transform( + mut self, + header_transform: Box<dyn HeaderTransform<N::BlockHeader>>, + ) -> Self { + self.header_transform = Some(header_transform); + self + } + /// Consumes the type and creates the actual [`NetworkConfig`] /// for the given client type that can interact with the chain. /// @@ -597,6 +612,7 @@ block_import, transactions_manager_config, nat, handshake, + header_transform, } = self;   let head = head.unwrap_or_else(|| Head { @@ -665,6 +681,7 @@ tx_gossip_disabled, transactions_manager_config, nat, handshake, + header_transform: header_transform.unwrap_or_else(|| Box::new(())), } } }
diff --git reth/crates/net/network/src/eth_requests.rs scroll-reth/crates/net/network/src/eth_requests.rs index d9ab711ea0dbbd945e4370eb7c157f9a1eded16b..492bf8bd55e4354632ef19d4e3a76b2e74b6be8f 100644 --- reth/crates/net/network/src/eth_requests.rs +++ scroll-reth/crates/net/network/src/eth_requests.rs @@ -116,7 +116,8 @@ }   match direction { HeadersDirection::Rising => { - if let Some(next) = number.checked_add(skip) { + if let Some(next) = number.checked_add(1).and_then(|n| n.checked_add(skip)) + { block = next.into() } else { break
diff --git reth/crates/net/network/src/fetch/mod.rs scroll-reth/crates/net/network/src/fetch/mod.rs index ece4fb626a4a85e16c29e1b4522440e2a238041d..794c184e69db7b2e24a684ef2bfbc89ed4b2f45e 100644 --- reth/crates/net/network/src/fetch/mod.rs +++ scroll-reth/crates/net/network/src/fetch/mod.rs @@ -4,7 +4,7 @@ mod client;   pub use client::FetchClient;   -use crate::{message::BlockRequest, session::BlockRangeInfo}; +use crate::{message::BlockRequest, session::BlockRangeInfo, transform::header::HeaderTransform}; use alloy_primitives::B256; use futures::StreamExt; use reth_eth_wire::{EthNetworkPrimitives, GetBlockBodies, GetBlockHeaders, NetworkPrimitives}; @@ -55,12 +55,18 @@ /// Receiver for new incoming download requests download_requests_rx: UnboundedReceiverStream<DownloadRequest<N>>, /// Sender for download requests, used to detach a [`FetchClient`] download_requests_tx: UnboundedSender<DownloadRequest<N>>, + /// A transformation hook applied to the downloaded headers. + header_transform: Box<dyn HeaderTransform<N::BlockHeader>>, }   // === impl StateSyncer ===   impl<N: NetworkPrimitives> StateFetcher<N> { - pub(crate) fn new(peers_handle: PeersHandle, num_active_peers: Arc<AtomicUsize>) -> Self { + pub(crate) fn new( + peers_handle: PeersHandle, + num_active_peers: Arc<AtomicUsize>, + header_transform: Box<dyn HeaderTransform<N::BlockHeader>>, + ) -> Self { let (download_requests_tx, download_requests_rx) = mpsc::unbounded_channel(); Self { inflight_headers_requests: Default::default(), @@ -71,6 +77,7 @@ num_active_peers, queued_requests: Default::default(), download_requests_rx: UnboundedReceiverStream::new(download_requests_rx), download_requests_tx, + header_transform, } }   @@ -272,8 +279,10 @@ let is_likely_bad_response = resp.as_ref().is_some_and(|r| res.is_likely_bad_headers_response(&r.request));   if let Some(resp) = resp { - // delegate the response - let _ = resp.response.send(res.map(|h| (peer_id, h).into())); + // apply the header transform and delegate the response + let _ = resp.response.send(res.map(|h| { + (peer_id, h.into_iter().map(|h| self.header_transform.map(h)).collect()).into() + })); }   if let Some(peer) = self.peers.get_mut(&peer_id) { @@ -484,8 +493,11 @@ #[tokio::test(flavor = "multi_thread")] async fn test_poll_fetcher() { let manager = PeersManager::new(PeersConfig::default()); - let mut fetcher = - StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default()); + let mut fetcher = StateFetcher::<EthNetworkPrimitives>::new( + manager.handle(), + Default::default(), + Box::new(()), + );   poll_fn(move |cx| { assert!(fetcher.poll(cx).is_pending()); @@ -506,8 +518,11 @@ #[tokio::test] async fn test_peer_rotation() { let manager = PeersManager::new(PeersConfig::default()); - let mut fetcher = - StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default()); + let mut fetcher = StateFetcher::<EthNetworkPrimitives>::new( + manager.handle(), + Default::default(), + Box::new(()), + ); // Add a few random peers let peer1 = B512::random(); let peer2 = B512::random(); @@ -530,8 +545,11 @@ #[tokio::test] async fn test_peer_prioritization() { let manager = PeersManager::new(PeersConfig::default()); - let mut fetcher = - StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default()); + let mut fetcher = StateFetcher::<EthNetworkPrimitives>::new( + manager.handle(), + Default::default(), + Box::new(()), + ); // Add a few random peers let peer1 = B512::random(); let peer2 = B512::random(); @@ -556,8 +574,11 @@ #[tokio::test] async fn test_on_block_headers_response() { let manager = PeersManager::new(PeersConfig::default()); - let mut fetcher = - StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default()); + let mut fetcher = StateFetcher::<EthNetworkPrimitives>::new( + manager.handle(), + Default::default(), + Box::new(()), + ); let peer_id = B512::random();   assert_eq!(fetcher.on_block_headers_response(peer_id, Ok(vec![Header::default()])), None); @@ -587,8 +608,11 @@ #[tokio::test] async fn test_header_response_outcome() { let manager = PeersManager::new(PeersConfig::default()); - let mut fetcher = - StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default()); + let mut fetcher = StateFetcher::<EthNetworkPrimitives>::new( + manager.handle(), + Default::default(), + Box::new(()), + ); let peer_id = B512::random();   let request_pair = || {
diff --git reth/crates/net/network/src/lib.rs scroll-reth/crates/net/network/src/lib.rs index a6de95512f84725d03255372170d43bf80530f44..ee7d503af09cd60034a750c49a27a0303aa5a3a3 100644 --- reth/crates/net/network/src/lib.rs +++ scroll-reth/crates/net/network/src/lib.rs @@ -130,6 +130,7 @@ pub mod message; pub mod peers; pub mod protocol; pub mod transactions; +pub mod transform;   mod budget; mod builder;
diff --git reth/crates/net/network/src/manager.rs scroll-reth/crates/net/network/src/manager.rs index ce8cda2b259ced236710b718326a491ef0bd9bc9..bcbdb2472abef86524ef60fe6caa10bda0503c39 100644 --- reth/crates/net/network/src/manager.rs +++ scroll-reth/crates/net/network/src/manager.rs @@ -21,7 +21,7 @@ config::NetworkConfig, discovery::Discovery, error::{NetworkError, ServiceKind}, eth_requests::IncomingEthRequest, - import::{BlockImport, BlockImportEvent, BlockImportOutcome, BlockValidation, NewBlockEvent}, + import::{BlockImportEvent, BlockImportOutcome, BlockValidation}, listener::ConnectionListener, message::{NewBlockMessage, PeerMessage}, metrics::{DisconnectMetrics, NetworkMetrics, NETWORK_POOL_TRANSACTIONS_SCOPE}, @@ -39,9 +39,11 @@ use futures::{Future, StreamExt}; use parking_lot::Mutex; use reth_chainspec::EnrForkIdEntry; use reth_eth_wire::{DisconnectReason, EthNetworkPrimitives, NetworkPrimitives}; +use reth_eth_wire_types::NewBlockPayload; use reth_fs_util::{self as fs, FsPathError}; use reth_metrics::common::mpsc::UnboundedMeteredSender; use reth_network_api::{ + block::NewBlockWithPeer, events::{PeerEvent, SessionInfo}, test_utils::PeersHandle, EthProtocolInfo, NetworkEvent, NetworkStatus, PeerInfo, PeerRequest, @@ -109,7 +111,7 @@ handle: NetworkHandle<N>, /// Receiver half of the command channel set up between this type and the [`NetworkHandle`] from_handle_rx: UnboundedReceiverStream<NetworkHandleMessage<N>>, /// Handles block imports according to the `eth` protocol. - block_import: Box<dyn BlockImport<N::NewBlockPayload>>, + block_import: EventSender<NewBlockWithPeer<N::Block>>, /// Sender for high level network events. event_sender: EventSender<NetworkEvent<PeerRequest<N>>>, /// Sender half to send events to the @@ -237,7 +239,7 @@ listener_addr, peers_config, sessions_config, chain_id, - block_import, + block_import: _, network_mode, boot_nodes, executor, @@ -250,6 +252,7 @@ tx_gossip_disabled, transactions_manager_config: _, nat, handshake, + header_transform, } = config;   let peers_manager = PeersManager::new(peers_config); @@ -311,6 +314,7 @@ crate::state::BlockNumReader::new(client), discovery, peers_manager, Arc::clone(&num_active_peers), + header_transform, );   let swarm = Swarm::new(incoming, sessions, state); @@ -339,7 +343,7 @@ Ok(Self { swarm, handle, from_handle_rx: UnboundedReceiverStream::new(from_handle_rx), - block_import, + block_import: EventSender::new(1000), event_sender, to_transactions_manager: None, to_eth_request_handler: None, @@ -534,6 +538,7 @@ } } }   + #[allow(dead_code)] /// Invoked after a `NewBlock` message from the peer was validated fn on_block_import_result(&mut self, event: BlockImportEvent<N::NewBlockPayload>) { match event { @@ -600,14 +605,16 @@ self.within_pow_or_disconnect(peer_id, |this| { // update peer's state, to track what blocks this peer has seen this.swarm.state_mut().on_new_block_hashes(peer_id, hashes.0.clone()); // start block import process for the hashes - this.block_import.on_new_block(peer_id, NewBlockEvent::Hashes(hashes)); + // this.block_import.on_new_block(peer_id, NewBlockEvent::Hashes(hashes)); }) } PeerMessage::NewBlock(block) => { self.within_pow_or_disconnect(peer_id, move |this| { this.swarm.state_mut().on_new_block(peer_id, block.hash); + let block = Arc::unwrap_or_clone(block.block); // start block import process - this.block_import.on_new_block(peer_id, NewBlockEvent::Block(block)); + this.block_import + .notify(NewBlockWithPeer { peer_id, block: block.block().clone() }); }); } PeerMessage::PooledTransactions(msg) => { @@ -638,6 +645,9 @@ /// Handler for received messages from a handle fn on_handle_message(&mut self, msg: NetworkHandleMessage<N>) { match msg { + NetworkHandleMessage::EthWireBlockListener(tx) => { + let _ = tx.send(self.block_import.new_listener()); + } NetworkHandleMessage::DiscoveryListener(tx) => { self.swarm.state_mut().discovery_mut().add_listener(tx); } @@ -1087,11 +1097,6 @@ let start = Instant::now(); let mut poll_durations = NetworkManagerPollDurations::default();   let this = self.get_mut(); - - // poll new block imports (expected to be a noop for POS) - while let Poll::Ready(outcome) = this.block_import.poll(cx) { - this.on_block_import_result(outcome); - }   // These loops drive the entire state of network and does a lot of work. Under heavy load // (many messages/events), data may arrive faster than it can be processed (incoming
diff --git reth/crates/net/network/src/network.rs scroll-reth/crates/net/network/src/network.rs index cfc3d56cb28dceebbd9cde934287d2dcc2a7e0e1..9e01cff13aeea0775b592f0d8601b28a69c0ef40 100644 --- reth/crates/net/network/src/network.rs +++ scroll-reth/crates/net/network/src/network.rs @@ -14,6 +14,7 @@ NewPooledTransactionHashes, SharedTransactions, }; use reth_ethereum_forks::Head; use reth_network_api::{ + block::{EthWireProvider, NewBlockWithPeer}, events::{NetworkPeersEvents, PeerEvent, PeerEventStream}, test_utils::{PeersHandle, PeersHandleProvider}, BlockDownloaderProvider, DiscoveryEvent, NetworkError, NetworkEvent, @@ -88,15 +89,18 @@ Self { inner: Arc::new(inner) } }   /// Returns the [`PeerId`] used in the network. + #[allow(clippy::missing_const_for_fn)] pub fn peer_id(&self) -> &PeerId { &self.inner.local_peer_id }   + #[allow(clippy::missing_const_for_fn)] fn manager(&self) -> &UnboundedSender<NetworkHandleMessage<N>> { &self.inner.to_manager_tx }   /// Returns the mode of the network, either pow, or pos + #[allow(clippy::missing_const_for_fn)] pub fn mode(&self) -> &NetworkMode { &self.inner.network_mode } @@ -182,11 +186,13 @@ self.send_message(NetworkHandleMessage::SetNetworkState(network_conn)); }   /// Whether tx gossip is disabled + #[allow(clippy::missing_const_for_fn)] pub fn tx_gossip_disabled(&self) -> bool { self.inner.tx_gossip_disabled }   /// Returns the secret key used for authenticating sessions. + #[allow(clippy::missing_const_for_fn)] pub fn secret_key(&self) -> &SecretKey { &self.inner.secret_key } @@ -216,6 +222,20 @@ fn discovery_listener(&self) -> UnboundedReceiverStream<DiscoveryEvent> { let (tx, rx) = mpsc::unbounded_channel(); let _ = self.manager().send(NetworkHandleMessage::DiscoveryListener(tx)); UnboundedReceiverStream::new(rx) + } +} + +impl<N: NetworkPrimitives> EthWireProvider<N> for NetworkHandle<N> { + async fn eth_wire_block_listener( + &self, + ) -> Result<EventStream<NewBlockWithPeer<N::Block>>, oneshot::error::RecvError> { + let (tx, rx) = oneshot::channel(); + self.send_message(NetworkHandleMessage::EthWireBlockListener(tx)); + rx.await + } + + fn eth_wire_announce_block(&self, block: N::NewBlockPayload, hash: B256) { + self.announce_block(block, hash) } }   @@ -548,4 +568,6 @@ /// Connect to the given peer. ConnectPeer(PeerId, PeerKind, PeerAddr), /// Message to update the node's advertised block range information. InternalBlockRangeUpdate(BlockRangeUpdate), + /// Retries a eth wire new block event listener. + EthWireBlockListener(oneshot::Sender<EventStream<NewBlockWithPeer<N::Block>>>), }
diff --git reth/crates/net/network/src/state.rs scroll-reth/crates/net/network/src/state.rs index 57d1a73198eeba2c29d9073974f71d943deb7508..8168a88cbf4c5eab4a41a4757e3229cfd038a8e4 100644 --- reth/crates/net/network/src/state.rs +++ scroll-reth/crates/net/network/src/state.rs @@ -7,6 +7,7 @@ fetch::{BlockResponseOutcome, FetchAction, StateFetcher}, message::{BlockRequest, NewBlockMessage, PeerResponse, PeerResponseResult}, peers::{PeerAction, PeersManager}, session::BlockRangeInfo, + transform::header::HeaderTransform, FetchClient, }; use alloy_consensus::BlockHeader; @@ -102,8 +103,10 @@ client: BlockNumReader, discovery: Discovery, peers_manager: PeersManager, num_active_peers: Arc<AtomicUsize>, + header_transform: Box<dyn HeaderTransform<N::BlockHeader>>, ) -> Self { - let state_fetcher = StateFetcher::new(peers_manager.handle(), num_active_peers); + let state_fetcher = + StateFetcher::new(peers_manager.handle(), num_active_peers, header_transform); Self { active_peers: Default::default(), peers_manager, @@ -596,7 +599,7 @@ peers_manager: Default::default(), queued_messages: Default::default(), client: BlockNumReader(Box::new(NoopProvider::default())), discovery: Discovery::noop(), - state_fetcher: StateFetcher::new(handle, Default::default()), + state_fetcher: StateFetcher::new(handle, Default::default(), Box::new(())), } }
diff --git reth/crates/net/network/src/test_utils/testnet.rs scroll-reth/crates/net/network/src/test_utils/testnet.rs index d24668995433610faee391afb4916ecdcbd865d8..3e3c63d59b302cbb9d2ea7c7c9698f66851daa60 100644 --- reth/crates/net/network/src/test_utils/testnet.rs +++ scroll-reth/crates/net/network/src/test_utils/testnet.rs @@ -104,6 +104,7 @@ &mut self.peers }   /// Return a slice of all peers. + #[allow(clippy::missing_const_for_fn)] pub fn peers(&self) -> &[Peer<C, Pool>] { &self.peers } @@ -354,6 +355,7 @@ rx.await.unwrap() }   /// Returns the [`PeerHandle`]s of this [`Testnet`]. + #[allow(clippy::missing_const_for_fn)] pub fn peers(&self) -> &[PeerHandle<Pool>] { &self.peers }
diff --git reth/crates/net/network/src/transform/header.rs scroll-reth/crates/net/network/src/transform/header.rs new file mode 100644 index 0000000000000000000000000000000000000000..ef5e777fea0ed536f0913f68378f39e71909e4a8 --- /dev/null +++ scroll-reth/crates/net/network/src/transform/header.rs @@ -0,0 +1,15 @@ +//! Abstraction over a transformation applied to headers. + +use reth_primitives_traits::BlockHeader; + +/// An instance of the trait applies a mapping to the input header. +pub trait HeaderTransform<H: BlockHeader>: std::fmt::Debug + Send + Sync { + /// Applies a mapping to the input header. + fn map(&self, header: H) -> H; +} + +impl<H: BlockHeader> HeaderTransform<H> for () { + fn map(&self, header: H) -> H { + header + } +}
diff --git reth/crates/net/network/src/transform/mod.rs scroll-reth/crates/net/network/src/transform/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..2081c2bd939cc50a1accdacd545427963b9fb1b7 --- /dev/null +++ scroll-reth/crates/net/network/src/transform/mod.rs @@ -0,0 +1,4 @@ +//! Provides an abstraction over transformation hooks that can be applied to data response from +//! peers. + +pub mod header;
diff --git reth/crates/net/p2p/src/full_block.rs scroll-reth/crates/net/p2p/src/full_block.rs index 8dbf3ce5690f89b60f386d8d7cd8c36419ceb71a..30a5588533309d604c44cf90fa95225d4bf88d14 100644 --- reth/crates/net/p2p/src/full_block.rs +++ scroll-reth/crates/net/p2p/src/full_block.rs @@ -10,7 +10,7 @@ }; use alloy_consensus::BlockHeader; use alloy_primitives::{Sealable, B256}; use core::marker::PhantomData; -use reth_consensus::{Consensus, ConsensusError}; +use reth_consensus::{Consensus, ConsensusError, HeaderValidator}; use reth_eth_wire_types::{EthNetworkPrimitives, HeadersDirection, NetworkPrimitives}; use reth_network_peers::{PeerId, WithPeerId}; use reth_primitives_traits::{SealedBlock, SealedHeader};
diff --git reth/crates/net/peers/src/bootnodes/mod.rs scroll-reth/crates/net/peers/src/bootnodes/mod.rs index e510e0b1cda2cc8dedc793703107497342c21891..17fa1741d8dbc646ff09c239df0bf73d0643fc4e 100644 --- reth/crates/net/peers/src/bootnodes/mod.rs +++ scroll-reth/crates/net/peers/src/bootnodes/mod.rs @@ -9,6 +9,9 @@ mod optimism; pub use optimism::*;   +mod scroll; +pub use scroll::*; + /// Returns parsed mainnet nodes pub fn mainnet_nodes() -> Vec<NodeRecord> { parse_nodes(&MAINNET_BOOTNODES[..]) @@ -47,6 +50,16 @@ /// Returns parsed op-stack base testnet nodes pub fn base_testnet_nodes() -> Vec<NodeRecord> { parse_nodes(OP_TESTNET_BOOTNODES) +} + +/// Returns parsed scroll mainnet nodes +pub fn scroll_nodes() -> Vec<NodeRecord> { + parse_nodes(SCROLL_BOOTNODES) +} + +/// Returns parsed scroll seplo nodes +pub fn scroll_sepolia_nodes() -> Vec<NodeRecord> { + parse_nodes(SCROLL_SEPOLIA_BOOTNODES) }   /// Parses all the nodes
diff --git reth/crates/net/peers/src/bootnodes/scroll.rs scroll-reth/crates/net/peers/src/bootnodes/scroll.rs new file mode 100644 index 0000000000000000000000000000000000000000..7d85fcc8d077b6ea67ea3a1c5426620fb2185500 --- /dev/null +++ scroll-reth/crates/net/peers/src/bootnodes/scroll.rs @@ -0,0 +1,17 @@ +//! Scroll bootnodes come from <https://github.com/scroll-tech/go-ethereum/blob/develop/params/bootnodes.go> + +/// Scroll mainnet boot nodes. +pub static SCROLL_BOOTNODES: &[&str] = &[ + "enode://c6ac91f43df3d63916ac1ae411cdd5ba249d55d48a7bec7f8cd5bb351a31aba437e5a69e8a1de74d73fdfeba8af1cfe9caf9846ecd3abf60d1ffdf4925b55b23@54.186.123.248:30303", + "enode://fdcc807b5d1353f3a1e98b90208ce6ef1b7d446136e51eaa8ad657b55518a2f8b37655e42375d61622e6ea18f3faf9d070c9bbdf012cf5484bcbad33b7a15fb1@44.227.91.206:30303", + "enode://6beb5a3efbb39be73d17630b6da48e94c0ce7ec665172111463cb470197b20c12faa1fa6f835b81c28571277d1017e65c4e426cc92a46141cf69118ecf28ac03@44.237.194.52:30303", + "enode://7cf893d444eb8e129dca0f6485b3df579911606e7c728be4fa55fcc5f155a37c3ce07d217ccec5447798bde465ac2bdba2cb8763d107e9f3257e787579e9f27e@52.35.203.107:30303", + "enode://c7b2d94e95da343db6e667a01cef90376a592f2d277fbcbf6e9c9186734ed8003d01389571bd10cdbab7a6e5adfa6f0c7b55644d0db24e0b9deb4ec80f842075@54.70.236.187:30303", +]; + +/// Scroll sepolia boot nodes. +pub static SCROLL_SEPOLIA_BOOTNODES: &[&str] = &[ + "enode://ceb1636bac5cbb262e5ad5b2cd22014bdb35ffe7f58b3506970d337a63099481814a338dbcd15f2d28757151e3ecd40ba38b41350b793cd0d910ff0436654f8c@35.85.84.250:30303", + "enode://29cee709c400533ae038a875b9ca975c8abef9eade956dcf3585e940acd5c0ae916968f514bd37d1278775aad1b7db30f7032a70202a87fd7365bd8de3c9f5fc@44.242.39.33:30303", + "enode://dd1ac5433c5c2b04ca3166f4cb726f8ff6d2da83dbc16d9b68b1ea83b7079b371eb16ef41c00441b6e85e32e33087f3b7753ea9e8b1e3f26d3e4df9208625e7f@54.148.111.168:30303", +];
diff --git reth/crates/node/builder/src/builder/mod.rs scroll-reth/crates/node/builder/src/builder/mod.rs index 118ead96ee9664e44ed2704c81c52fafc22ee320..5014576761a6c0ddd8acf765064af23699f00c28 100644 --- reth/crates/node/builder/src/builder/mod.rs +++ scroll-reth/crates/node/builder/src/builder/mod.rs @@ -6,7 +6,7 @@ use crate::{ common::WithConfigs, components::NodeComponentsBuilder, node::FullNode, - rpc::{RethRpcAddOns, RethRpcServerHandles, RpcContext}, + rpc::{RethRpcAddOns, RethRpcServerHandles, RpcContext, RpcHandleProvider}, BlockReaderFor, DebugNode, DebugNodeLauncher, EngineNodeLauncher, LaunchNode, Node, }; use alloy_eips::eip4844::env_settings::EnvKzgSettings; @@ -49,6 +49,14 @@ /// The adapter type for a reth node with the builtin provider type // Note: we need to hardcode this because custom components might depend on it in associated types. pub type RethFullAdapter<DB, Types> = FullNodeTypesAdapter<Types, DB, BlockchainProvider<NodeTypesWithDBAdapter<Types, DB>>>; + +/// A full node adapter for a reth node with the builtin provider type +type FullNodeAdapter<N, DB> = NodeAdapter< + RethFullAdapter<DB, N>, + <<N as Node<RethFullAdapter<DB, N>>>::ComponentsBuilder as NodeComponentsBuilder< + RethFullAdapter<DB, N>, + >>::Components, +>;   #[expect(clippy::doc_markdown)] #[cfg_attr(doc, aquamarine::aquamarine)] @@ -391,11 +399,10 @@ >>::Node, > where N: Node<RethFullAdapter<DB, N>, ChainSpec = ChainSpec> + NodeTypesForProvider, - N::AddOns: RethRpcAddOns< - NodeAdapter< - RethFullAdapter<DB, N>, - <N::ComponentsBuilder as NodeComponentsBuilder<RethFullAdapter<DB, N>>>::Components, - >, + N::AddOns: RethRpcAddOns<FullNodeAdapter<N, DB>>, + <N::AddOns as NodeAddOns<FullNodeAdapter<N, DB>>>::Handle: RpcHandleProvider< + FullNodeAdapter<N, DB>, + <N::AddOns as RethRpcAddOns<FullNodeAdapter<N, DB>>>::EthApi, >, N::Primitives: FullNodePrimitives, EngineNodeLauncher: LaunchNode< @@ -448,6 +455,12 @@ where T: FullNodeTypes, CB: NodeComponentsBuilder<T>, AO: RethRpcAddOns<NodeAdapter<T, CB::Components>>, + <AO as NodeAddOns< + NodeAdapter<T, <CB as NodeComponentsBuilder<T>>::Components>, + >>::Handle: RpcHandleProvider< + NodeAdapter<T, <CB as NodeComponentsBuilder<T>>::Components>, + <AO as RethRpcAddOns<NodeAdapter<T, <CB as NodeComponentsBuilder<T>>::Components>>>::EthApi, + >, { /// Returns a reference to the node builder's config. pub const fn config(&self) -> &NodeConfig<<T::Types as NodeTypes>::ChainSpec> {
diff --git reth/crates/node/builder/src/builder/states.rs scroll-reth/crates/node/builder/src/builder/states.rs index bbb3e250917a166b90d50a08f6051370ead0afe3..140802aafb218bb47cd1d693c23ad6e331966c01 100644 --- reth/crates/node/builder/src/builder/states.rs +++ scroll-reth/crates/node/builder/src/builder/states.rs @@ -9,7 +9,7 @@ use crate::{ components::{NodeComponents, NodeComponentsBuilder}, hooks::NodeHooks, launch::LaunchNode, - rpc::{RethRpcAddOns, RethRpcServerHandles, RpcContext}, + rpc::{RethRpcAddOns, RethRpcServerHandles, RpcContext, RpcHandleProvider}, AddOns, ComponentsFor, FullNode, };   @@ -249,6 +249,12 @@ where T: FullNodeTypes, CB: NodeComponentsBuilder<T>, AO: RethRpcAddOns<NodeAdapter<T, CB::Components>>, + <AO as NodeAddOns< + NodeAdapter<T, <CB as NodeComponentsBuilder<T>>::Components>, + >>::Handle: RpcHandleProvider< + NodeAdapter<T, <CB as NodeComponentsBuilder<T>>::Components>, + <AO as RethRpcAddOns<NodeAdapter<T, <CB as NodeComponentsBuilder<T>>::Components>>>::EthApi, + >, { /// Launches the node with the given launcher. pub async fn launch_with<L>(self, launcher: L) -> eyre::Result<L::Node> @@ -298,7 +304,7 @@ use reth_ethereum_engine_primitives::EthEngineTypes; use reth_evm::noop::NoopEvmConfig; use reth_evm_ethereum::MockEvmConfig; use reth_network::EthNetworkPrimitives; - use reth_network_api::noop::NoopNetwork; + use reth_network_api::{self, noop::NoopNetwork}; use reth_node_api::FullNodeTypesAdapter; use reth_node_ethereum::EthereumNode; use reth_payload_builder::PayloadBuilderHandle;
diff --git reth/crates/node/builder/src/components/pool.rs scroll-reth/crates/node/builder/src/components/pool.rs index 2d431831ee3e0bf79e2db01ad5b9ffe5ec78d669..c12de25a3a20a78a182beb2861b6882f550354ef 100644 --- reth/crates/node/builder/src/components/pool.rs +++ scroll-reth/crates/node/builder/src/components/pool.rs @@ -1,15 +1,16 @@ //! Pool component for the node builder.   +use crate::{BuilderContext, FullNodeTypes}; + use alloy_primitives::Address; use reth_chain_state::CanonStateSubscriptions; +use reth_chainspec::ChainSpecProvider; use reth_node_api::TxTy; use reth_transaction_pool::{ blobstore::DiskFileBlobStore, CoinbaseTipOrdering, PoolConfig, PoolTransaction, SubPoolLimit, TransactionPool, TransactionValidationTaskExecutor, TransactionValidator, }; use std::{collections::HashSet, future::Future}; - -use crate::{BuilderContext, FullNodeTypes};   /// A type that knows how to build the transaction pool. pub trait PoolBuilder<Node: FullNodeTypes>: Send { @@ -236,11 +237,13 @@ Pool::Transaction: PoolTransaction<Consensus = TxTy<Node::Types>>, { let chain_events = ctx.provider().canonical_state_stream(); let client = ctx.provider().clone(); + let chain_spec = client.chain_spec();   ctx.task_executor().spawn_critical( "txpool maintenance task", reth_transaction_pool::maintain::maintain_transaction_pool_future( client, + chain_spec, pool, chain_events, ctx.task_executor().clone(),
diff --git reth/crates/node/builder/src/handle.rs scroll-reth/crates/node/builder/src/handle.rs index 2997a8687afdb3114c52255e0ac4407fea725b0e..112c85408b1abf7a67c82164e3c747bb7f548843 100644 --- reth/crates/node/builder/src/handle.rs +++ scroll-reth/crates/node/builder/src/handle.rs @@ -3,11 +3,18 @@ use reth_node_api::FullNodeComponents; use reth_node_core::exit::NodeExitFuture;   -use crate::{node::FullNode, rpc::RethRpcAddOns}; +use crate::{ + node::FullNode, + rpc::{RethRpcAddOns, RpcHandleProvider}, +};   /// A Handle to the launched node. #[must_use = "Needs to await the node exit future"] -pub struct NodeHandle<Node: FullNodeComponents, AddOns: RethRpcAddOns<Node>> { +pub struct NodeHandle<Node: FullNodeComponents, AddOns: RethRpcAddOns<Node>> +where + <AddOns as reth_node_api::NodeAddOns<Node>>::Handle: + RpcHandleProvider<Node, <AddOns as RethRpcAddOns<Node>>::EthApi>, +{ /// All node components. pub node: FullNode<Node, AddOns>, /// The exit future of the node. @@ -18,6 +25,8 @@ impl<Node, AddOns> NodeHandle<Node, AddOns> where Node: FullNodeComponents, AddOns: RethRpcAddOns<Node>, + <AddOns as reth_node_api::NodeAddOns<Node>>::Handle: + RpcHandleProvider<Node, <AddOns as RethRpcAddOns<Node>>::EthApi>, { /// Waits for the node to exit, if it was configured to exit. pub async fn wait_for_node_exit(self) -> eyre::Result<()> { @@ -29,6 +38,8 @@ impl<Node, AddOns> fmt::Debug for NodeHandle<Node, AddOns> where Node: FullNodeComponents, AddOns: RethRpcAddOns<Node>, + <AddOns as reth_node_api::NodeAddOns<Node>>::Handle: + RpcHandleProvider<Node, <AddOns as RethRpcAddOns<Node>>::EthApi>, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("NodeHandle")
diff --git reth/crates/node/builder/src/launch/debug.rs scroll-reth/crates/node/builder/src/launch/debug.rs index bbe6a7a6b7240e338c650edf9f2b70bfa836eb6e..687717d2705415c97ced2e4ee7eb918dc28d877e 100644 --- reth/crates/node/builder/src/launch/debug.rs +++ scroll-reth/crates/node/builder/src/launch/debug.rs @@ -1,5 +1,8 @@ use super::LaunchNode; -use crate::{rpc::RethRpcAddOns, EngineNodeLauncher, Node, NodeHandle}; +use crate::{ + rpc::{RethRpcAddOns, RpcHandleProvider}, + EngineNodeLauncher, Node, NodeHandle, +}; use alloy_provider::network::AnyNetwork; use jsonrpsee::core::{DeserializeOwned, Serialize}; use reth_chainspec::EthChainSpec; @@ -109,10 +112,16 @@ where N: FullNodeComponents<Types: DebugNode<N>>, AddOns: RethRpcAddOns<N>, L: LaunchNode<Target, Node = NodeHandle<N, AddOns>>, + <AddOns as reth_node_api::NodeAddOns<N>>::Handle: + RpcHandleProvider<N, <AddOns as RethRpcAddOns<N>>::EthApi>, { type Node = NodeHandle<N, AddOns>;   - async fn launch_node(self, target: Target) -> eyre::Result<Self::Node> { + async fn launch_node(self, target: Target) -> eyre::Result<Self::Node> + where + <AddOns as reth_node_api::NodeAddOns<N>>::Handle: + RpcHandleProvider<N, <AddOns as RethRpcAddOns<N>>::EthApi>, + { let handle = self.inner.launch_node(target).await?;   let config = &handle.node.config; @@ -130,7 +139,7 @@ }) .await?;   let rpc_consensus_client = DebugConsensusClient::new( - handle.node.add_ons_handle.beacon_engine_handle.clone(), + handle.node.rpc_handle().beacon_engine_handle.clone(), Arc::new(block_provider), );   @@ -162,7 +171,7 @@ chain.id(), N::Types::rpc_to_primitive_block, ); let rpc_consensus_client = DebugConsensusClient::new( - handle.node.add_ons_handle.beacon_engine_handle.clone(), + handle.node.rpc_handle().beacon_engine_handle.clone(), Arc::new(block_provider), ); handle.node.task_executor.spawn_critical("etherscan consensus client", async move { @@ -175,7 +184,7 @@ info!(target: "reth::cli", "Using local payload attributes builder for dev mode");   let blockchain_db = handle.node.provider.clone(); let chain_spec = config.chain.clone(); - let beacon_engine_handle = handle.node.add_ons_handle.beacon_engine_handle.clone(); + let beacon_engine_handle = handle.node.rpc_handle().beacon_engine_handle.clone(); let pool = handle.node.pool.clone(); let payload_builder_handle = handle.node.payload_builder_handle.clone();
diff --git reth/crates/node/builder/src/launch/engine.rs scroll-reth/crates/node/builder/src/launch/engine.rs index 62e0ff2f50a23acabf4f4e4bd94ec932d08371dc..80850730497d108cdfeeec67c755328047d6da31 100644 --- reth/crates/node/builder/src/launch/engine.rs +++ scroll-reth/crates/node/builder/src/launch/engine.rs @@ -3,7 +3,7 @@ use crate::{ common::{Attached, LaunchContextWith, WithConfigs}, hooks::NodeHooks, - rpc::{EngineValidatorAddOn, EngineValidatorBuilder, RethRpcAddOns, RpcHandle}, + rpc::{EngineValidatorAddOn, EngineValidatorBuilder, RethRpcAddOns, RpcHandleProvider}, setup::build_networked_pipeline, AddOns, AddOnsContext, FullNode, LaunchContext, LaunchNode, NodeAdapter, NodeBuilderWithComponents, NodeComponents, NodeComponentsBuilder, NodeHandle, NodeTypesAdapter, @@ -75,6 +75,12 @@ >, CB: NodeComponentsBuilder<T>, AO: RethRpcAddOns<NodeAdapter<T, CB::Components>> + EngineValidatorAddOn<NodeAdapter<T, CB::Components>>, + <AO as reth_node_api::NodeAddOns< + NodeAdapter<T, <CB as NodeComponentsBuilder<T>>::Components>, + >>::Handle: RpcHandleProvider< + NodeAdapter<T, <CB as NodeComponentsBuilder<T>>::Components>, + <AO as RethRpcAddOns<NodeAdapter<T, <CB as NodeComponentsBuilder<T>>::Components>>>::EthApi, + >, { type Node = NodeHandle<NodeAdapter<T, CB::Components>, AO>;   @@ -251,8 +257,7 @@ events, )), );   - let RpcHandle { rpc_server_handles, rpc_registry, engine_events, beacon_engine_handle } = - add_ons.launch_add_ons(add_ons_ctx).await?; + let add_ons_handle = add_ons.launch_add_ons(add_ons_ctx).await?;   // Run consensus engine to completion let initial_target = ctx.initial_backfill_target()?; @@ -345,12 +350,7 @@ payload_builder_handle: ctx.components().payload_builder_handle().clone(), task_executor: ctx.task_executor().clone(), config: ctx.node_config().clone(), data_dir: ctx.data_dir().clone(), - add_ons_handle: RpcHandle { - rpc_server_handles, - rpc_registry, - engine_events, - beacon_engine_handle, - }, + add_ons_handle, }; // Notify on node started on_node_started.on_event(FullNode::clone(&full_node))?;
diff --git reth/crates/node/builder/src/node.rs scroll-reth/crates/node/builder/src/node.rs index 01ca760bc5b467d9dea6b6696c1b45f4086f9b59..a4d603c724711fb1427c2c4cbc04249102d2896d 100644 --- reth/crates/node/builder/src/node.rs +++ scroll-reth/crates/node/builder/src/node.rs @@ -1,6 +1,7 @@ // re-export the node api types pub use reth_node_api::{FullNodeTypes, NodeTypes};   +use super::rpc::RpcHandleProvider; use crate::{components::NodeComponentsBuilder, rpc::RethRpcAddOns, NodeAdapter, NodeAddOns}; use reth_node_api::{EngineTypes, FullNodeComponents, PayloadTypes}; use reth_node_core::{ @@ -156,15 +157,17 @@ where Payload: PayloadTypes, Node: FullNodeComponents<Types: NodeTypes<Payload = Payload>>, AddOns: RethRpcAddOns<Node>, + <AddOns as reth_node_api::NodeAddOns<Node>>::Handle: + RpcHandleProvider<Node, <AddOns as RethRpcAddOns<Node>>::EthApi>, { /// Returns the [`RpcServerHandle`] to the started rpc server. - pub const fn rpc_server_handle(&self) -> &RpcServerHandle { - &self.add_ons_handle.rpc_server_handles.rpc + pub fn rpc_server_handle(&self) -> &RpcServerHandle { + &self.add_ons_handle.rpc_handle().rpc_server_handles.rpc }   /// Returns the [`AuthServerHandle`] to the started authenticated engine API server. - pub const fn auth_server_handle(&self) -> &AuthServerHandle { - &self.add_ons_handle.rpc_server_handles.auth + pub fn auth_server_handle(&self) -> &AuthServerHandle { + &self.add_ons_handle.rpc_handle().rpc_server_handles.auth } }   @@ -173,6 +176,8 @@ where Engine: EngineTypes, Node: FullNodeComponents<Types: NodeTypes<Payload = Engine>>, AddOns: RethRpcAddOns<Node>, + <AddOns as reth_node_api::NodeAddOns<Node>>::Handle: + RpcHandleProvider<Node, <AddOns as RethRpcAddOns<Node>>::EthApi>, { /// Returns the [`EngineApiClient`] interface for the authenticated engine API. ///
diff --git reth/crates/node/builder/src/rpc.rs scroll-reth/crates/node/builder/src/rpc.rs index cb84c536617015c629442aec7823636d34947245..ec9832d7e970a95d38a5d6392008507d78f858cf 100644 --- reth/crates/node/builder/src/rpc.rs +++ scroll-reth/crates/node/builder/src/rpc.rs @@ -362,6 +362,20 @@ .finish() } }   +/// Trait to provide access to the RPC handle. +pub trait RpcHandleProvider<Node: FullNodeComponents, EthApi: EthApiTypes> { + /// Returns the rpc server handles. + fn rpc_handle(&self) -> &RpcHandle<Node, EthApi>; +} + +impl<Node: FullNodeComponents, EthApi: EthApiTypes> RpcHandleProvider<Node, EthApi> + for RpcHandle<Node, EthApi> +{ + fn rpc_handle(&self) -> &Self { + self + } +} + /// Handle returned when only the regular RPC server (HTTP/WS/IPC) is launched. /// /// This handle provides access to the RPC server endpoints and registry, but does not @@ -432,7 +446,7 @@ > { /// Additional RPC add-ons. pub hooks: RpcHooks<Node, EthB::EthApi>, /// Builder for `EthApi` - eth_api_builder: EthB, + pub eth_api_builder: EthB, /// Payload validator builder payload_validator_builder: PVB, /// Builder for `EngineApi` @@ -993,8 +1007,9 @@ }   /// Helper trait implemented for add-ons producing [`RpcHandle`]. Used by common node launcher /// implementations. -pub trait RethRpcAddOns<N: FullNodeComponents>: - NodeAddOns<N, Handle = RpcHandle<N, Self::EthApi>> +pub trait RethRpcAddOns<N: FullNodeComponents>: NodeAddOns<N> +where + Self::Handle: RpcHandleProvider<N, Self::EthApi>, { /// eth API implementation. type EthApi: EthApiTypes;
diff --git reth/crates/node/core/src/args/payload_builder.rs scroll-reth/crates/node/core/src/args/payload_builder.rs index f751bcc070ccf6629d9af6fcaf5d7b3959df6da0..d658241c21c9c4c06b7d7f143c045204fc209a8b 100644 --- reth/crates/node/core/src/args/payload_builder.rs +++ scroll-reth/crates/node/core/src/args/payload_builder.rs @@ -5,7 +5,7 @@ use clap::{ builder::{RangedU64ValueParser, TypedValueParser}, Arg, Args, Command, }; -use reth_cli_util::{parse_duration_from_secs, parse_duration_from_secs_or_ms}; +use reth_cli_util::parse_duration_from_secs_or_ms; use std::{borrow::Cow, ffi::OsStr, time::Duration};   /// Parameters for configuring the Payload Builder @@ -29,7 +29,7 @@ #[arg(long = "builder.interval", value_parser = parse_duration_from_secs_or_ms, default_value = "1", value_name = "DURATION")] pub interval: Duration,   /// The deadline for when the payload builder job should resolve. - #[arg(long = "builder.deadline", value_parser = parse_duration_from_secs, default_value = "12", value_name = "SECONDS")] + #[arg(long = "builder.deadline", value_parser = parse_duration_from_secs_or_ms, default_value = "12s", value_name = "DEADLINE")] pub deadline: Duration,   /// Maximum number of tasks to spawn for building a payload.
diff --git reth/crates/payload/primitives/Cargo.toml scroll-reth/crates/payload/primitives/Cargo.toml index bb305961fe1db30af6f793835daec93a7473aaca..3953554c456b6342e6a1bf16c0bc8ea7a2229c7d 100644 --- reth/crates/payload/primitives/Cargo.toml +++ scroll-reth/crates/payload/primitives/Cargo.toml @@ -22,7 +22,8 @@ # alloy alloy-eips.workspace = true alloy-primitives.workspace = true alloy-rpc-types-engine = { workspace = true, features = ["serde"] } -op-alloy-rpc-types-engine = { workspace = true, optional = true } +op-alloy-rpc-types-engine = { workspace = true, optional = true, features = ["serde"] } +scroll-alloy-rpc-types-engine = { workspace = true, optional = true, features = ["serde"] }   # misc auto_impl.workspace = true @@ -44,8 +45,10 @@ "op-alloy-rpc-types-engine?/std", "serde/std", "thiserror/std", "reth-primitives-traits/std", + "scroll-alloy-rpc-types-engine?/std", ] op = [ "dep:op-alloy-rpc-types-engine", "reth-primitives-traits/op", ] +scroll-alloy-traits = ["dep:scroll-alloy-rpc-types-engine", "reth-primitives-traits/scroll-alloy-traits"]
diff --git reth/crates/payload/primitives/src/traits.rs scroll-reth/crates/payload/primitives/src/traits.rs index 868929c2b1bef9f8fd74d7994172544b518f654e..4301fbe19617e354a5bd45ca47d7060bcbec8b81 100644 --- reth/crates/payload/primitives/src/traits.rs +++ scroll-reth/crates/payload/primitives/src/traits.rs @@ -138,6 +138,21 @@ self.payload_attributes.parent_beacon_block_root } }   +#[cfg(feature = "scroll-alloy-traits")] +impl PayloadAttributes for scroll_alloy_rpc_types_engine::ScrollPayloadAttributes { + fn timestamp(&self) -> u64 { + self.payload_attributes.timestamp + } + + fn withdrawals(&self) -> Option<&Vec<Withdrawal>> { + self.payload_attributes.withdrawals.as_ref() + } + + fn parent_beacon_block_root(&self) -> Option<B256> { + self.payload_attributes.parent_beacon_block_root + } +} + /// Factory trait for creating payload attributes. /// /// Enables different strategies for generating payload attributes based on
diff --git reth/crates/ress/provider/src/lib.rs scroll-reth/crates/ress/provider/src/lib.rs index 41318ebaaf1e5ce9b92ea3e139a210aab70afd28..cef54540b736def16ef2c1c15eae77b9f5c21d1e 100644 --- reth/crates/ress/provider/src/lib.rs +++ scroll-reth/crates/ress/provider/src/lib.rs @@ -154,7 +154,7 @@ // We allow block execution to fail, since we still want to record all accessed state by // invalid blocks. if let Err(error) = self.evm_config.batch_executor(&mut db).execute_with_state_closure( &block, - |state: &State<_>| { + |state: &mut State<_>| { record.record_executed_state(state); }, ) {
diff --git reth/crates/rpc/rpc-convert/Cargo.toml scroll-reth/crates/rpc/rpc-convert/Cargo.toml index abaf8d8d04b6110ac64514043b7cb3d449e737b0..7f87e9e721b9865195b0258d0e37694e3593f472 100644 --- reth/crates/rpc/rpc-convert/Cargo.toml +++ scroll-reth/crates/rpc/rpc-convert/Cargo.toml @@ -33,6 +33,13 @@ op-alloy-network = { workspace = true, optional = true } reth-optimism-primitives = { workspace = true, optional = true } op-revm = { workspace = true, optional = true }   +# scroll +scroll-alloy-consensus = { workspace = true, optional = true } +scroll-alloy-evm = { workspace = true, optional = true } +scroll-alloy-rpc-types = { workspace = true, optional = true } +reth-scroll-primitives = { workspace = true, optional = true } +revm-scroll = { workspace = true, optional = true } + # revm revm-context.workspace = true   @@ -54,3 +61,13 @@ "dep:op-revm", "reth-evm/op", "reth-primitives-traits/op", ] +scroll = [ + "dep:scroll-alloy-consensus", + "dep:scroll-alloy-evm", + "dep:scroll-alloy-rpc-types", + "dep:reth-scroll-primitives", + "dep:reth-storage-api", + "dep:revm-scroll", + "reth-evm/scroll-alloy-traits", + "reth-primitives-traits/scroll-alloy-traits", +]
diff --git reth/crates/rpc/rpc-convert/src/lib.rs scroll-reth/crates/rpc/rpc-convert/src/lib.rs index 5ea281c4ef8a47de3b69f008d297cb1b68e7c0e5..9fb9c40cd8e7d4ec4eccdbea8bc0b58d92ff1fd8 100644 --- reth/crates/rpc/rpc-convert/src/lib.rs +++ scroll-reth/crates/rpc/rpc-convert/src/lib.rs @@ -27,3 +27,6 @@ };   #[cfg(feature = "op")] pub use transaction::op::*; + +#[cfg(feature = "scroll")] +pub use transaction::scroll::*;
diff --git reth/crates/rpc/rpc-convert/src/rpc.rs scroll-reth/crates/rpc/rpc-convert/src/rpc.rs index bd5555a3013f210e46f69b0f344f71021234bb32..180f5150b6e8a9735a79570e997a139aa920c30c 100644 --- reth/crates/rpc/rpc-convert/src/rpc.rs +++ scroll-reth/crates/rpc/rpc-convert/src/rpc.rs @@ -130,3 +130,35 @@ }; Ok(signed) } } + +#[cfg(feature = "scroll")] +impl SignableTxRequest<scroll_alloy_consensus::ScrollTxEnvelope> + for scroll_alloy_rpc_types::ScrollTransactionRequest +{ + async fn try_build_and_sign( + self, + signer: impl TxSigner<Signature> + Send, + ) -> Result<scroll_alloy_consensus::ScrollTxEnvelope, SignTxRequestError> { + let mut tx = + self.build_typed_tx().map_err(|_| SignTxRequestError::InvalidTransactionRequest)?; + let signature = signer.sign_transaction(&mut tx).await?; + let signed = match tx { + scroll_alloy_consensus::ScrollTypedTransaction::Legacy(tx) => { + scroll_alloy_consensus::ScrollTxEnvelope::Legacy(tx.into_signed(signature)) + } + scroll_alloy_consensus::ScrollTypedTransaction::Eip2930(tx) => { + scroll_alloy_consensus::ScrollTxEnvelope::Eip2930(tx.into_signed(signature)) + } + scroll_alloy_consensus::ScrollTypedTransaction::Eip1559(tx) => { + scroll_alloy_consensus::ScrollTxEnvelope::Eip1559(tx.into_signed(signature)) + } + scroll_alloy_consensus::ScrollTypedTransaction::Eip7702(tx) => { + scroll_alloy_consensus::ScrollTxEnvelope::Eip7702(tx.into_signed(signature)) + } + scroll_alloy_consensus::ScrollTypedTransaction::L1Message(_) => { + return Err(SignTxRequestError::InvalidTransactionRequest); + } + }; + Ok(signed) + } +}
diff --git reth/crates/rpc/rpc-convert/src/transaction.rs scroll-reth/crates/rpc/rpc-convert/src/transaction.rs index cf845067410d5e26a99b348a0f5e5f041a913c82..95d19348f4f0ea59f723be85505dd9990565fb57 100644 --- reth/crates/rpc/rpc-convert/src/transaction.rs +++ scroll-reth/crates/rpc/rpc-convert/src/transaction.rs @@ -570,6 +570,78 @@ Ok(self.header_converter.convert_header(header, block_size)) } }   +/// Scroll specific RPC transaction compatibility implementations. +#[cfg(feature = "scroll")] +pub mod scroll { + use super::*; + use alloy_consensus::SignableTransaction; + use alloy_primitives::{Address, Bytes, Signature}; + use reth_primitives_traits::SignedTransaction; + use reth_scroll_primitives::ScrollReceipt; + use reth_storage_api::{errors::ProviderError, ReceiptProvider}; + use revm_scroll::l1block::TX_L1_FEE_PRECISION_U256; + use scroll_alloy_consensus::{ScrollAdditionalInfo, ScrollTransactionInfo, ScrollTxEnvelope}; + use scroll_alloy_rpc_types::ScrollTransactionRequest; + + /// Creates [`ScrollTransactionInfo`] by adding [`ScrollAdditionalInfo`] to [`TransactionInfo`] + /// if `tx` is not a L1 message. + pub fn try_into_scroll_tx_info<T: ReceiptProvider<Receipt = ScrollReceipt>>( + provider: &T, + tx: &ScrollTxEnvelope, + tx_info: TransactionInfo, + ) -> Result<ScrollTransactionInfo, ProviderError> { + let additional_info = if tx.is_l1_message() { + None + } else { + provider + .receipt_by_hash(*tx.tx_hash())? + .map(|receipt| ScrollAdditionalInfo { l1_fee: receipt.l1_fee() }) + } + .unwrap_or_default(); + + Ok(ScrollTransactionInfo::new(tx_info, additional_info)) + } + + impl FromConsensusTx<ScrollTxEnvelope> for scroll_alloy_rpc_types::Transaction { + type TxInfo = ScrollTransactionInfo; + + fn from_consensus_tx(tx: ScrollTxEnvelope, signer: Address, tx_info: Self::TxInfo) -> Self { + Self::from_transaction(Recovered::new_unchecked(tx, signer), tx_info) + } + } + + impl TryIntoSimTx<ScrollTxEnvelope> for ScrollTransactionRequest { + fn try_into_sim_tx(self) -> Result<ScrollTxEnvelope, ValueError<Self>> { + let tx = self + .build_typed_tx() + .map_err(|request| ValueError::new(request, "Required fields missing"))?; + + // Create an empty signature for the transaction. + let signature = Signature::new(Default::default(), Default::default(), false); + + Ok(tx.into_signed(signature).into()) + } + } + + impl TryIntoTxEnv<scroll_alloy_evm::ScrollTransactionIntoTxEnv<TxEnv>> + for ScrollTransactionRequest + { + type Err = EthTxEnvError; + + fn try_into_tx_env<Spec>( + self, + cfg_env: &CfgEnv<Spec>, + block_env: &BlockEnv, + ) -> Result<scroll_alloy_evm::ScrollTransactionIntoTxEnv<TxEnv>, Self::Err> { + Ok(scroll_alloy_evm::ScrollTransactionIntoTxEnv::new( + self.as_ref().clone().try_into_tx_env(cfg_env, block_env)?, + Some(Bytes::new()), + Some(TX_L1_FEE_PRECISION_U256), + )) + } + } +} + /// Optimism specific RPC transaction compatibility implementations. #[cfg(feature = "op")] pub mod op {
diff --git reth/crates/rpc/rpc-eth-api/Cargo.toml scroll-reth/crates/rpc/rpc-eth-api/Cargo.toml index 44637d1931c329f281744301250fcd7fdd2bc91f..a2293b4630975046a97003384551f15abcbe0025 100644 --- reth/crates/rpc/rpc-eth-api/Cargo.toml +++ scroll-reth/crates/rpc/rpc-eth-api/Cargo.toml @@ -31,6 +31,9 @@ reth-network-api.workspace = true reth-node-api.workspace = true reth-trie-common = { workspace = true, features = ["eip1186"] }   +# scroll +reth-scroll-evm = { workspace = true, optional = true } + # ethereum alloy-evm = { workspace = true, features = ["overrides", "call-util"] } alloy-rlp.workspace = true @@ -68,3 +71,4 @@ "reth-primitives-traits/op", "reth-rpc-convert/op", "alloy-evm/op", ] +scroll = ["reth-scroll-evm"]
diff --git reth/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs scroll-reth/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs index b564cd11b423152d26e22d2938d1a7f57d8135d4..d8fb3621ee4b9240689c2244d12ba325e19676f0 100644 --- reth/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs +++ scroll-reth/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs @@ -383,3 +383,17 @@ withdrawals: parent.withdrawals_root().map(|_| Default::default()), } } } + +#[cfg(feature = "scroll")] +impl<H: alloy_consensus::BlockHeader> BuildPendingEnv<H> + for reth_scroll_evm::ScrollNextBlockEnvAttributes +{ + fn build_pending_env(parent: &reth_primitives_traits::SealedHeader<H>) -> Self { + Self { + timestamp: parent.timestamp().saturating_add(1), + suggested_fee_recipient: parent.beneficiary(), + gas_limit: parent.gas_limit(), + base_fee: parent.base_fee_per_gas().unwrap_or_default(), + } + } +}
diff --git reth/crates/rpc/rpc-eth-types/src/fee_history.rs scroll-reth/crates/rpc/rpc-eth-types/src/fee_history.rs index 7838cc3304d032d852e6da0b116668c3894dc241..615437498ecccd0115384b329aaa92cd99c470e0 100644 --- reth/crates/rpc/rpc-eth-types/src/fee_history.rs +++ scroll-reth/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -51,6 +51,7 @@ }   /// How the cache is configured. #[inline] + #[allow(clippy::missing_const_for_fn)] pub fn config(&self) -> &FeeHistoryCacheConfig { &self.inner.config }
diff --git reth/crates/rpc/rpc-eth-types/src/gas_oracle.rs scroll-reth/crates/rpc/rpc-eth-types/src/gas_oracle.rs index 00df9f7360b1cbc951fcd7359ef8dc6f2845db2d..a024c894629cfbd17b31968fa8fdfc2e267396e0 100644 --- reth/crates/rpc/rpc-eth-types/src/gas_oracle.rs +++ scroll-reth/crates/rpc/rpc-eth-types/src/gas_oracle.rs @@ -3,7 +3,7 @@ //! previous blocks.   use super::{EthApiError, EthResult, EthStateCache, RpcInvalidTransactionError}; use alloy_consensus::{constants::GWEI_TO_WEI, BlockHeader, Transaction, TxReceipt}; -use alloy_eips::BlockNumberOrTag; +use alloy_eips::{BlockNumberOrTag, Encodable2718}; use alloy_primitives::{B256, U256}; use alloy_rpc_types_eth::BlockId; use derive_more::{Deref, DerefMut, From, Into}; @@ -205,7 +205,8 @@ price = max_price; } }   - inner.last_price = GasPriceOracleResult { block_hash: header.hash(), price }; + inner.last_price = + GasPriceOracleResult { block_hash: header.hash(), price, ..Default::default() };   Ok(price) } @@ -339,11 +340,149 @@ suggestion = max_price; } }   - inner.last_price = GasPriceOracleResult { block_hash: header.hash(), price: suggestion }; + inner.last_price = GasPriceOracleResult { + block_hash: header.hash(), + price: suggestion, + ..Default::default() + };   Ok(suggestion) }   + /// Suggests a max priority fee value using a simplified and more predictable algorithm + /// appropriate for chains like Scroll with a single known block builder. + /// + /// It returns either: + /// - The minimum suggested priority fee when blocks have capacity + /// - 10% above the median effective priority fee from the last block when at capacity + /// + /// A block is considered at capacity if its total gas used plus the maximum single transaction + /// gas would exceed the block's gas limit, or the total block payload size plus the maximum + /// single transaction would exceed the block's payload size limit. + pub async fn scroll_suggest_tip_cap( + &self, + min_suggested_priority_fee: U256, + payload_size_limit: u64, + ) -> EthResult<U256> { + let (result, _) = self + .calculate_suggest_tip_cap( + BlockNumberOrTag::Latest, + min_suggested_priority_fee, + payload_size_limit, + ) + .await; + result + } + + /// Calculates a gas price suggestion and returns whether the block is at capacity. + /// + /// This method implements the core logic for suggesting gas prices based on block capacity. + /// It returns a tuple containing the suggested gas price and a boolean indicating + /// whether the latest block is at capacity (gas limit or payload size limit). + pub async fn calculate_suggest_tip_cap( + &self, + block_number_or_tag: BlockNumberOrTag, + min_suggested_priority_fee: U256, + payload_size_limit: u64, + ) -> (EthResult<U256>, bool) { + let header = match self.provider.sealed_header_by_number_or_tag(block_number_or_tag) { + Ok(Some(header)) => header, + Ok(None) => return (Err(EthApiError::HeaderNotFound(BlockId::latest())), false), + Err(e) => return (Err(e.into()), false), + }; + + let mut inner = self.inner.lock().await; + + // if we have stored a last price, then we check whether or not it was for the same head + if inner.last_price.block_hash == header.hash() { + return (Ok(inner.last_price.price), inner.last_price.is_at_capacity); + } + + let mut suggestion = min_suggested_priority_fee; + let mut is_at_capacity = false; + + // find the maximum gas used by any of the transactions in the block and + // the maximum and total payload size used by the transactions in the block to use as + // the capacity margin for the block, if no receipts or block are found return the + // suggested_min_priority_fee + let (block, receipts) = match self.cache.get_block_and_receipts(header.hash()).await { + Ok(Some((block, receipts))) => (block, receipts), + Ok(None) => return (Ok(suggestion), false), + Err(e) => return (Err(e.into()), false), + }; + + let mut max_tx_gas_used = 0u64; + let mut last_cumulative_gas = 0; + for receipt in receipts.as_ref() { + let cumulative_gas = receipt.cumulative_gas_used(); + // get the gas used by each transaction in the block, by subtracting the + // cumulative gas used of the previous transaction from the cumulative gas used of + // the current transaction. This is because there is no gas_used() + // method on the Receipt trait. + let gas_used = cumulative_gas - last_cumulative_gas; + max_tx_gas_used = max_tx_gas_used.max(gas_used); + last_cumulative_gas = cumulative_gas; + } + + let transactions = block.transactions_recovered(); + + // Calculate payload sizes for all transactions + let mut max_tx_payload_size = 0u64; + let mut total_payload_size = 0u64; + + for tx in transactions { + // Get the EIP-2718 encoded length as payload size + let payload_size = tx.encode_2718_len() as u64; + max_tx_payload_size = max_tx_payload_size.max(payload_size); + total_payload_size += payload_size; + } + + // sanity check the max gas used and transaction size value + if max_tx_gas_used > header.gas_limit() { + warn!(target: "scroll::gas_price_oracle", ?max_tx_gas_used, "Found tx consuming more gas than the block limit"); + return (Ok(suggestion), is_at_capacity); + } + if max_tx_payload_size > payload_size_limit { + warn!(target: "scroll::gas_price_oracle", ?max_tx_payload_size, "Found tx consuming more size than the block size limit"); + return (Ok(suggestion), is_at_capacity); + } + + // if the block is at capacity, the suggestion must be increased + if header.gas_used() + max_tx_gas_used > header.gas_limit() || + total_payload_size + max_tx_payload_size > payload_size_limit + { + let median_tip = match self.get_block_median_tip(header.hash()).await { + Ok(Some(median_tip)) => median_tip, + Ok(None) => return (Ok(suggestion), is_at_capacity), + Err(e) => return (Err(e), is_at_capacity), + }; + + let new_suggestion = median_tip + median_tip / U256::from(10); + + if new_suggestion > suggestion { + suggestion = new_suggestion; + } + is_at_capacity = true; + } + + // constrain to the max price + if let Some(max_price) = self.oracle_config.max_price { + if suggestion > max_price { + suggestion = max_price; + } + } + + // update the cache only if it's latest block header + if block_number_or_tag == BlockNumberOrTag::Latest { + inner.last_price = GasPriceOracleResult { + block_hash: header.hash(), + price: suggestion, + is_at_capacity, + }; + } + (Ok(suggestion), is_at_capacity) + } + /// Get the median tip value for the given block. This is useful for determining /// tips when a block is at capacity. /// @@ -408,11 +547,13 @@ /// The block hash that the oracle used to calculate the price pub block_hash: B256, /// The price that the oracle calculated pub price: U256, + /// Whether the latest block is at capacity + pub is_at_capacity: bool, }   impl Default for GasPriceOracleResult { fn default() -> Self { - Self { block_hash: B256::ZERO, price: U256::from(GWEI_TO_WEI) } + Self { block_hash: B256::ZERO, price: U256::from(GWEI_TO_WEI), is_at_capacity: false } } }
diff --git reth/crates/rpc/rpc/Cargo.toml scroll-reth/crates/rpc/rpc/Cargo.toml index e0d1fcb601f9e2ed3bbdc03abffec83972e3b8c4..304849a9a171e5bceb8af936530b8ef8411cea17 100644 --- reth/crates/rpc/rpc/Cargo.toml +++ scroll-reth/crates/rpc/rpc/Cargo.toml @@ -64,6 +64,9 @@ alloy-serde.workspace = true revm = { workspace = true, features = ["optional_block_gas_limit", "optional_eip3607", "optional_no_base_fee"] } revm-primitives = { workspace = true, features = ["serde"] }   +# scroll +reth-scroll-evm = { workspace = true, optional = true } + # rpc jsonrpsee.workspace = true http.workspace = true @@ -104,3 +107,4 @@ jsonrpsee = { workspace = true, features = ["client"] }   [features] js-tracer = ["revm-inspectors/js-tracer", "reth-rpc-eth-types/js-tracer"] +scroll = ["reth-scroll-evm"]
diff --git reth/crates/rpc/rpc/src/debug.rs scroll-reth/crates/rpc/rpc/src/debug.rs index e562f208e1f9b2dd43358f78a44a923f86f7ffe5..c10c632653416f5c333f800afd26cc3e11f8dc15 100644 --- reth/crates/rpc/rpc/src/debug.rs +++ scroll-reth/crates/rpc/rpc/src/debug.rs @@ -639,11 +639,18 @@ let block_executor = this.eth_api().evm_config().executor(db);   let mut witness_record = ExecutionWitnessRecord::default();   + let mut withdraw_root_res: Result<_, reth_errors::ProviderError> = Ok(()); let _ = block_executor - .execute_with_state_closure(&block, |statedb: &State<_>| { + .execute_with_state_closure(&block, |statedb: &mut State<_>| { + #[cfg(feature = "scroll")] + { + use reth_scroll_evm::LoadWithdrawRoot; + withdraw_root_res = statedb.load_withdraw_root(); + } witness_record.record_executed_state(statedb); }) .map_err(|err| EthApiError::Internal(err.into()))?; + withdraw_root_res?;   let ExecutionWitnessRecord { hashed_state, codes, keys, lowest_block_number } = witness_record;
diff --git reth/crates/rpc/rpc/src/eth/bundle.rs scroll-reth/crates/rpc/rpc/src/eth/bundle.rs index 0ff7fb1dde466c9427375bcef70c3e4dfa739abf..f43d787f169a0b03f34e7ce11afc8b3ce6580ab3 100644 --- reth/crates/rpc/rpc/src/eth/bundle.rs +++ scroll-reth/crates/rpc/rpc/src/eth/bundle.rs @@ -34,6 +34,7 @@ Self { inner: Arc::new(EthBundleInner { eth_api, blocking_task_guard }) } }   /// Access the underlying `Eth` API. + #[allow(clippy::missing_const_for_fn)] pub fn eth_api(&self) -> &Eth { &self.inner.eth_api }
diff --git reth/crates/rpc/rpc/src/eth/filter.rs scroll-reth/crates/rpc/rpc/src/eth/filter.rs index ff5841c3747bd2422b7a75bacdeddbfa45d7ceee..b995d389dce11281c835bba258a6fc9131ed19c3 100644 --- reth/crates/rpc/rpc/src/eth/filter.rs +++ scroll-reth/crates/rpc/rpc/src/eth/filter.rs @@ -153,6 +153,7 @@ eth_filter }   /// Returns all currently active filters + #[allow(clippy::missing_const_for_fn)] pub fn active_filters(&self) -> &ActiveFilters<RpcTransaction<Eth::NetworkTypes>> { &self.inner.active_filters }
diff --git reth/crates/rpc/rpc/src/eth/sim_bundle.rs scroll-reth/crates/rpc/rpc/src/eth/sim_bundle.rs index 67d94d8140f93accf8b8b4a4c8e1817a2b505186..bdd23e44ffaf7c9b3164ff891fd888b43b5eae28 100644 --- reth/crates/rpc/rpc/src/eth/sim_bundle.rs +++ scroll-reth/crates/rpc/rpc/src/eth/sim_bundle.rs @@ -73,6 +73,7 @@ Self { inner: Arc::new(EthSimBundleInner { eth_api, blocking_task_guard }) } }   /// Access the underlying `Eth` API. + #[allow(clippy::missing_const_for_fn)] pub fn eth_api(&self) -> &Eth { &self.inner.eth_api }
diff --git reth/crates/rpc/rpc/src/reth.rs scroll-reth/crates/rpc/rpc/src/reth.rs index 8f8decd7f4aac9b617dc848ab2ed4567b036787f..3aaa1ebc5e0b61269a0e29ba0364aa872704ff29 100644 --- reth/crates/rpc/rpc/src/reth.rs +++ scroll-reth/crates/rpc/rpc/src/reth.rs @@ -27,6 +27,7 @@ // === impl RethApi ===   impl<Provider> RethApi<Provider> { /// The provider that can interact with the chain. + #[allow(clippy::missing_const_for_fn)] pub fn provider(&self) -> &Provider { &self.inner.provider }
diff --git reth/crates/rpc/rpc/src/trace.rs scroll-reth/crates/rpc/rpc/src/trace.rs index 1ae984bc9a77e7f0d7262e7d50a3fcd73f174c61..2c5b0ed143bafb41539415aa20f595515657722e 100644 --- reth/crates/rpc/rpc/src/trace.rs +++ scroll-reth/crates/rpc/rpc/src/trace.rs @@ -64,6 +64,7 @@ self.inner.blocking_task_guard.clone().acquire_owned().await }   /// Access the underlying `Eth` API. + #[allow(clippy::missing_const_for_fn)] pub fn eth_api(&self) -> &Eth { &self.inner.eth_api }
diff --git reth/crates/rpc/rpc/src/validation.rs scroll-reth/crates/rpc/rpc/src/validation.rs index 0b484fd13a8fb9934d68732df9ba65b9e8f9b7eb..ef6381e4dcd3bb83bae050ec65bb443870cf1b36 100644 --- reth/crates/rpc/rpc/src/validation.rs +++ scroll-reth/crates/rpc/rpc/src/validation.rs @@ -16,7 +16,7 @@ use core::fmt; use jsonrpsee::core::RpcResult; use jsonrpsee_types::error::ErrorObject; use reth_chainspec::{ChainSpecProvider, EthereumHardforks}; -use reth_consensus::{Consensus, FullConsensus}; +use reth_consensus::{Consensus, FullConsensus, HeaderValidator}; use reth_engine_primitives::PayloadValidator; use reth_errors::{BlockExecutionError, ConsensusError, ProviderError}; use reth_evm::{execute::Executor, ConfigureEvm}; @@ -207,7 +207,7 @@ let state_root = state_provider.state_root(state_provider.hashed_post_state(&output.state))?;   - if state_root != block.header().state_root() { + if self.consensus.validate_state_root(block.header(), state_root).is_err() { return Err(ConsensusError::BodyStateRootDiff( GotExpected { got: state_root, expected: block.header().state_root() }.into(), )
diff --git reth/crates/transaction-pool/Cargo.toml scroll-reth/crates/transaction-pool/Cargo.toml index 5409bb102e1b66061e52e52d242ce8b9edf35f76..637ad907d074f8879e24e2c24cf06e5960f34b73 100644 --- reth/crates/transaction-pool/Cargo.toml +++ scroll-reth/crates/transaction-pool/Cargo.toml @@ -60,6 +60,7 @@ proptest = { workspace = true, optional = true } proptest-arbitrary-interop = { workspace = true, optional = true }   [dev-dependencies] +alloy-consensus = { workspace = true, features = ["k256"] } reth-provider = { workspace = true, features = ["test-utils"] } reth-tracing.workspace = true alloy-primitives = { workspace = true, features = ["rand"] }
diff --git reth/crates/transaction-pool/src/lib.rs scroll-reth/crates/transaction-pool/src/lib.rs index 8825c0c8814661065bd9c414e22350398afd8045..410fbda20bed377c02fe2bcf40a2aeeb6a23979c 100644 --- reth/crates/transaction-pool/src/lib.rs +++ scroll-reth/crates/transaction-pool/src/lib.rs @@ -250,9 +250,10 @@ //! TransactionValidationTaskExecutor::eth(client.clone(), blob_store.clone(), executor.clone()), //! blob_store, //! Default::default(), //! ); +//! let chainspec = client.chain_spec(); //! //! // spawn a task that listens for new blocks and updates the pool's transactions, mined transactions etc.. -//! tokio::task::spawn(maintain_transaction_pool_future(client, pool, stream, executor.clone(), Default::default())); +//! tokio::task::spawn(maintain_transaction_pool_future(client, chainspec, pool, stream, executor.clone(), Default::default())); //! //! # } //! ``` @@ -352,6 +353,7 @@ Self { pool: Arc::new(PoolInner::new(validator, ordering, blob_store, config)) } }   /// Returns the wrapped pool. + #[allow(clippy::missing_const_for_fn)] pub(crate) fn inner(&self) -> &PoolInner<V, T, S> { &self.pool }
diff --git reth/crates/transaction-pool/src/maintain.rs scroll-reth/crates/transaction-pool/src/maintain.rs index 300ff6eb4109c722a49da31ce2d7ebfe6b25a9f4..a462e37351a022d9eb2be09cf309e157e6461d16 100644 --- reth/crates/transaction-pool/src/maintain.rs +++ scroll-reth/crates/transaction-pool/src/maintain.rs @@ -22,7 +22,10 @@ use reth_fs_util::FsPathError; use reth_primitives_traits::{ transaction::signed::SignedTransaction, NodePrimitives, SealedHeader, }; -use reth_storage_api::{errors::provider::ProviderError, BlockReaderIdExt, StateProviderFactory}; +use reth_storage_api::{ + errors::provider::ProviderError, BaseFeeProvider, BlockReaderIdExt, StateProviderBox, + StateProviderFactory, +}; use reth_tasks::TaskSpawner; use serde::{Deserialize, Serialize}; use std::{ @@ -92,8 +95,9 @@ } }   /// Returns a spawnable future for maintaining the state of the transaction pool. -pub fn maintain_transaction_pool_future<N, Client, P, St, Tasks>( +pub fn maintain_transaction_pool_future<N, Client, BaseFee, P, St, Tasks>( client: Client, + base_fee_provider: BaseFee, pool: P, events: St, task_spawner: Tasks, @@ -106,12 +110,14 @@ + BlockReaderIdExt<Header = N::BlockHeader> + ChainSpecProvider<ChainSpec: EthChainSpec<Header = N::BlockHeader>> + Clone + 'static, + BaseFee: BaseFeeProvider<StateProviderBox> + Send + 'static, P: TransactionPoolExt<Transaction: PoolTransaction<Consensus = N::SignedTx>> + 'static, St: Stream<Item = CanonStateNotification<N>> + Send + Unpin + 'static, Tasks: TaskSpawner + 'static, { async move { - maintain_transaction_pool(client, pool, events, task_spawner, config).await; + maintain_transaction_pool(client, base_fee_provider, pool, events, task_spawner, config) + .await; } .boxed() } @@ -119,8 +125,9 @@ /// Maintains the state of the transaction pool by handling new blocks and reorgs. /// /// This listens for any new blocks and reorgs and updates the transaction pool's state accordingly -pub async fn maintain_transaction_pool<N, Client, P, St, Tasks>( +pub async fn maintain_transaction_pool<N, Client, BaseFee, P, St, Tasks>( client: Client, + base_fee_provider: BaseFee, pool: P, mut events: St, task_spawner: Tasks, @@ -132,6 +139,7 @@ + BlockReaderIdExt<Header = N::BlockHeader> + ChainSpecProvider<ChainSpec: EthChainSpec<Header = N::BlockHeader>> + Clone + 'static, + BaseFee: BaseFeeProvider<StateProviderBox> + Send + 'static, P: TransactionPoolExt<Transaction: PoolTransaction<Consensus = N::SignedTx>> + 'static, St: Stream<Item = CanonStateNotification<N>> + Send + Unpin + 'static, Tasks: TaskSpawner + 'static, @@ -142,13 +150,13 @@ // ensure the pool points to latest state if let Ok(Some(latest)) = client.header_by_number_or_tag(BlockNumberOrTag::Latest) { let latest = SealedHeader::seal_slow(latest); let chain_spec = client.chain_spec(); + let base_fee = pool_pending_base_fee(&client, &base_fee_provider, latest.header()); + let info = BlockInfo { block_gas_limit: latest.gas_limit(), last_seen_block_hash: latest.hash(), last_seen_block_number: latest.number(), - pending_basefee: chain_spec - .next_block_base_fee(latest.header(), latest.timestamp()) - .unwrap_or_default(), + pending_basefee: base_fee, pending_blob_fee: latest .maybe_next_block_blob_fee(chain_spec.blob_params_at_timestamp(latest.timestamp())), }; @@ -326,9 +334,9 @@ let chain_spec = client.chain_spec();   // fees for the next block: `new_tip+1` - let pending_block_base_fee = chain_spec - .next_block_base_fee(new_tip.header(), new_tip.timestamp()) - .unwrap_or_default(); + let pending_block_base_fee = + pool_pending_base_fee(&client, &base_fee_provider, new_tip.header()); + let pending_block_blob_fee = new_tip.header().maybe_next_block_blob_fee( chain_spec.blob_params_at_timestamp(new_tip.timestamp()), ); @@ -429,9 +437,8 @@ let tip = blocks.tip(); let chain_spec = client.chain_spec();   // fees for the next block: `tip+1` - let pending_block_base_fee = chain_spec - .next_block_base_fee(tip.header(), tip.timestamp()) - .unwrap_or_default(); + let pending_block_base_fee = + pool_pending_base_fee(&client, &base_fee_provider, tip.header()); let pending_block_blob_fee = tip.header().maybe_next_block_blob_fee( chain_spec.blob_params_at_timestamp(tip.timestamp()), ); @@ -499,6 +506,24 @@ blob_store_tracker.add_new_chain_blocks(&blocks); } } } +} + +/// Computes the pending base fee for the pool. +fn pool_pending_base_fee< + Client: StateProviderFactory + BlockReaderIdExt, + BaseFee: BaseFeeProvider<StateProviderBox>, + H: BlockHeader, +>( + client: &Client, + base_fee_provider: &BaseFee, + parent_header: &H, +) -> u64 { + let provider = client.state_by_block_id(parent_header.number().into()); + provider + .and_then(|mut p| { + base_fee_provider.next_block_base_fee(&mut p, &parent_header, parent_header.timestamp()) + }) + .unwrap_or_else(|_| parent_header.base_fee_per_gas().unwrap_or_default()) }   struct FinalizedBlockTracker {
diff --git reth/crates/transaction-pool/src/pool/mod.rs scroll-reth/crates/transaction-pool/src/pool/mod.rs index cf330853c878411932ebd8b09060a14575683aee..53df964a4725872714d03cbaeb456ac31401a20a 100644 --- reth/crates/transaction-pool/src/pool/mod.rs +++ scroll-reth/crates/transaction-pool/src/pool/mod.rs @@ -1076,6 +1076,7 @@ PendingTransactionIter { kind, iter } }   /// Returns if the transaction should be propagated. + #[allow(clippy::missing_const_for_fn)] pub(crate) fn is_propagate_allowed(&self) -> bool { self.transaction.propagate } @@ -1166,6 +1167,7 @@ } }   /// Returns the discarded transactions if there were any + #[allow(clippy::missing_const_for_fn)] pub(crate) fn discarded_transactions(&self) -> Option<&[Arc<ValidPoolTransaction<T>>]> { match self { Self::Pending(tx) => Some(&tx.discarded), @@ -1208,6 +1210,7 @@ }   /// Returns the [`TransactionId`] of the added transaction #[cfg(test)] + #[allow(clippy::missing_const_for_fn)] pub(crate) fn id(&self) -> &TransactionId { match self { Self::Pending(added) => added.transaction.id(),
diff --git reth/crates/transaction-pool/src/pool/pending.rs scroll-reth/crates/transaction-pool/src/pool/pending.rs index 594f5f82f1b510c553f8ed6c1e6f8627a1d231e2..f2cd6a6fde5d869da83f364b7a964659fd4bbcbe 100644 --- reth/crates/transaction-pool/src/pool/pending.rs +++ scroll-reth/crates/transaction-pool/src/pool/pending.rs @@ -580,6 +580,7 @@ }   impl<T: TransactionOrdering> PendingTransaction<T> { /// The next transaction of the sender: `nonce + 1` + #[allow(clippy::missing_const_for_fn)] pub(crate) fn unlocks(&self) -> TransactionId { self.transaction.transaction_id.descendant() }
diff --git reth/crates/transaction-pool/src/validate/eth.rs scroll-reth/crates/transaction-pool/src/validate/eth.rs index b32401f2cbb88d2c63f03d382bb8893ac8012b80..fe152b057b1f98c4f5010a580a08570caa8a7ec2 100644 --- reth/crates/transaction-pool/src/validate/eth.rs +++ scroll-reth/crates/transaction-pool/src/validate/eth.rs @@ -58,6 +58,7 @@ self.client().chain_spec() }   /// Returns the configured client + #[allow(clippy::missing_const_for_fn)] pub fn client(&self) -> &Client { &self.inner.client } @@ -243,6 +244,7 @@ // === impl EthTransactionValidatorInner ===   impl<Client: ChainSpecProvider, Tx> EthTransactionValidatorInner<Client, Tx> { /// Returns the configured chain id + #[allow(clippy::missing_const_for_fn)] pub(crate) fn chain_id(&self) -> u64 { self.client.chain_spec().chain().id() } @@ -1168,11 +1170,17 @@ SpecId::SHANGHAI } else { SpecId::MERGE }; + // TODO(scroll): SpecId is starting to leak from revm to reth. Find a solution to avoid + // having to add `is_eip_7702_enabled` everywhere. + let is_eip7702_enabled = true; + let is_eip7623_enabled = true;   let gas = revm_interpreter::gas::calculate_initial_tx_gas( spec_id, transaction.input(), transaction.is_create(), + is_eip7702_enabled, + is_eip7623_enabled, transaction.access_list().map(|l| l.len()).unwrap_or_default() as u64, transaction .access_list()
diff --git reth/deny.toml scroll-reth/deny.toml index fd2eb5c11cdf24e638c0e0470a571b26f9d8500c..82bacf4df6d3095f9ad49927f152fd752a421146 100644 --- reth/deny.toml +++ scroll-reth/deny.toml @@ -20,7 +20,7 @@ # Lint level for when a crate version requirement is `*` wildcards = "allow" highlight = "all" # List of crates to deny -deny = [{ name = "openssl" }] +# TODO issue #201 deny = [{ name = "openssl" }] # Certain crates/versions that will be skipped when doing duplicate detection. skip = [] # Similarly to `skip` allows you to skip certain crates during duplicate @@ -62,8 +62,23 @@ exceptions = [ # TODO: decide on MPL-2.0 handling # These dependencies are grandfathered in https://github.com/paradigmxyz/reth/pull/6980 { allow = ["MPL-2.0"], name = "option-ext" }, - { allow = ["MPL-2.0"], name = "webpki-root-certs" }, +] + +# Skip the poseidon-bn254, bn254 and zktrie crates for license verification. We should at some point publish a license for them. +[licenses.private] +ignore = true +ignore-sources = [ + "https://github.com/scroll-tech/poseidon-bn254", + "https://github.com/scroll-tech/bn254", + "https://github.com/scroll-tech/zktrie.git", + "https://github.com/scroll-tech/scroll-revm.git", + "https://github.com/scroll-tech/da-codec.git", ] + +[[licenses.clarify]] +name = "ring" +expression = "LicenseRef-ring" +license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }]   [[licenses.clarify]] name = "rustls-webpki" @@ -89,4 +104,10 @@ "https://github.com/paradigmxyz/revm-inspectors", "https://github.com/alloy-rs/evm", "https://github.com/alloy-rs/hardforks", "https://github.com/paradigmxyz/jsonrpsee", + "https://github.com/scroll-tech/bn254", + "https://github.com/scroll-tech/sp1-intrinsics", + "https://github.com/scroll-tech/poseidon-bn254", + "https://github.com/scroll-tech/scroll-revm.git", + "https://github.com/scroll-tech/revm.git", + "https://github.com/scroll-tech/da-codec.git", ]
diff --git reth/docs/vocs/docs/pages/cli/reth/node.mdx scroll-reth/docs/vocs/docs/pages/cli/reth/node.mdx index 83c954d886af5a9935381659221b36ac3f9e3d75..caad09bd5fb94d95f994427326f90b02b345300e 100644 --- reth/docs/vocs/docs/pages/cli/reth/node.mdx +++ scroll-reth/docs/vocs/docs/pages/cli/reth/node.mdx @@ -586,10 +586,10 @@ Interval is specified in seconds or in milliseconds if the value ends with `ms`: * `50ms` -> 50 milliseconds * `1` -> 1 second   [default: 1]   - --builder.deadline <SECONDS> + --builder.deadline <DEADLINE> The deadline for when the payload builder job should resolve   - [default: 12] + [default: 12s]   --builder.max-tasks <MAX_PAYLOAD_TASKS> Maximum number of tasks to spawn for building a payload
diff --git reth/examples/custom-node-components/src/main.rs scroll-reth/examples/custom-node-components/src/main.rs index b6b8fb3cdf2e7c499f0f097b9fbbd194e74be845..c4e571762e8709e037d66be350d3b5bb2e3fb878 100644 --- reth/examples/custom-node-components/src/main.rs +++ scroll-reth/examples/custom-node-components/src/main.rs @@ -95,6 +95,7 @@ ctx.task_executor().spawn_critical( "txpool maintenance task", reth_ethereum::pool::maintain::maintain_transaction_pool_future( client, + ctx.chain_spec(), pool, chain_events, ctx.task_executor().clone(),
diff --git reth/examples/custom-node/Cargo.toml scroll-reth/examples/custom-node/Cargo.toml index 5b340a0e493c5b129915e767cca2dd2f2552c397..abb3a7f2fcb62045ef503ea94d61d88025dfd2e5 100644 --- reth/examples/custom-node/Cargo.toml +++ scroll-reth/examples/custom-node/Cargo.toml @@ -15,6 +15,7 @@ reth-optimism-forks.workspace = true reth-db-api.workspace = true reth-op = { workspace = true, features = ["node", "pool", "rpc"] } reth-payload-builder.workspace = true +reth-primitives-traits.workspace = true reth-rpc-api.workspace = true reth-engine-primitives.workspace = true reth-rpc-engine-api.workspace = true @@ -43,7 +44,6 @@ op-revm.workspace = true   # misc async-trait.workspace = true -derive_more.workspace = true eyre.workspace = true jsonrpsee.workspace = true serde.workspace = true @@ -70,5 +70,6 @@ "alloy-rpc-types-engine/arbitrary", "reth-db-api/arbitrary", "alloy-rpc-types-eth/arbitrary", "op-alloy-rpc-types/arbitrary", + "reth-primitives-traits/arbitrary", ] default = []
diff --git reth/examples/custom-node/src/primitives/header.rs scroll-reth/examples/custom-node/src/primitives/header.rs index 0a832d690c9cdb38e6b34ee1905a849b73c16dc8..acf80bb26e59c17ca14bc7040a045f518e68ce06 100644 --- reth/examples/custom-node/src/primitives/header.rs +++ scroll-reth/examples/custom-node/src/primitives/header.rs @@ -181,6 +181,12 @@ Ok(obj) } }   +impl reth_primitives_traits::block::header::BlockHeaderMut for CustomHeader { + fn extra_data_mut(&mut self) -> &mut Bytes { + &mut self.inner.extra_data + } +} + impl BlockHeader for CustomHeader {}   impl RlpBincode for CustomHeader {}
diff --git reth/testing/ef-tests/src/cases/blockchain_test.rs scroll-reth/testing/ef-tests/src/cases/blockchain_test.rs index 471d6655e25858dde03d4ac4be007c46766e4273..933ff6613cd93e50c9ea7a3ec52b7b5555021a27 100644 --- reth/testing/ef-tests/src/cases/blockchain_test.rs +++ scroll-reth/testing/ef-tests/src/cases/blockchain_test.rs @@ -237,7 +237,7 @@ let state_db = StateProviderDatabase(&state_provider); let executor = executor_provider.batch_executor(state_db);   let output = executor - .execute_with_state_closure(&(*block).clone(), |statedb: &State<_>| { + .execute_with_state_closure(&(*block).clone(), |statedb: &mut State<_>| { witness_record.record_executed_state(statedb); }) .map_err(|err| Error::block_failed(block_number, err))?;
diff --git reth/.github/assets/check_rv32imac.sh scroll-reth/.github/assets/check_rv32imac.sh index 9d9c421ca208c14d9f85466a5cb993eefc0a54b4..49bea6a900d96c540288e18633fbd6533ddbe672 100755 --- reth/.github/assets/check_rv32imac.sh +++ scroll-reth/.github/assets/check_rv32imac.sh @@ -36,6 +36,14 @@ reth-optimism-forks reth-optimism-consensus reth-optimism-primitives reth-optimism-evm + + ## scroll + reth-scroll-chainspec + scroll-alloy-consensus + scroll-alloy-evm + scroll-alloy-rpc-types + scroll-alloy-rpc-types-engine + )   # Array to hold the results
diff --git reth/.github/assets/check_wasm.sh scroll-reth/.github/assets/check_wasm.sh index e140d01e7965ac5acc885fc8d18300a816170a8d..8f32c00f17d22f208e7048cf01556c028ecd1f81 100755 --- reth/.github/assets/check_wasm.sh +++ scroll-reth/.github/assets/check_wasm.sh @@ -79,6 +79,18 @@ reth-era-downloader # tokio reth-era-utils # tokio reth-tracing-otlp reth-node-ethstats + reth-scroll-cli # tokio + reth-scroll-node # tokio + reth-scroll # tokio + reth-scroll-state-commitment # tokio + reth-scroll-chainspec # tokio + reth-scroll-consensus # c-kzg + reth-scroll-evm # tokio + reth-scroll-rpc #tokio + reth-scroll-engine-primitives # proptest + reth-scroll-payload # c-kzg + reth-scroll-primitives # c-kzg + reth-scroll-txpool )   # Array to hold the results
diff --git reth/.github/workflows/bench.yml scroll-reth/.github/workflows/bench.yml index 43bb159dfd3ae86cfde61288bf1bc7f5fbd6c684..2ce5c45dd28b422d71adb49b4c779e0e77464681 100644 --- reth/.github/workflows/bench.yml +++ scroll-reth/.github/workflows/bench.yml @@ -5,7 +5,7 @@ pull_request: # TODO: Disabled temporarily for https://github.com/CodSpeedHQ/runner/issues/55 # merge_group: push: - branches: [main] + branches: [scroll]   env: CARGO_TERM_COLOR: always @@ -15,8 +15,7 @@ name: bench jobs: codspeed: - runs-on: - group: Reth + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: @@ -36,4 +35,3 @@ - name: Run the benchmarks uses: CodSpeedHQ/action@v3 with: run: cargo codspeed run --workspace - token: ${{ secrets.CODSPEED_TOKEN }}
diff --git reth/.github/workflows/book.yml scroll-reth/.github/workflows/book.yml index 0c0bf1e053b934f5be2cb13582964a5d620d93da..ac21b08526b5f81caae231a1b0567b172a41e511 100644 --- reth/.github/workflows/book.yml +++ scroll-reth/.github/workflows/book.yml @@ -4,9 +4,9 @@ name: book   on: push: - branches: [main] + branches: [main, scroll] pull_request: - branches: [main] + branches: [main, scroll] types: [opened, reopened, synchronize, closed] merge_group:
diff --git reth/.github/workflows/compact.yml scroll-reth/.github/workflows/compact.yml index 06ddec20cb7c3b730b05f37ed6c7ef7ffa26ec4a..b8ac94471c5e68a6213274af007fd21cb43e58ce 100644 --- reth/.github/workflows/compact.yml +++ scroll-reth/.github/workflows/compact.yml @@ -9,7 +9,7 @@ on: pull_request: merge_group: push: - branches: [main] + branches: [scroll]   env: CARGO_TERM_COLOR: always @@ -17,13 +17,13 @@ name: compact-codec jobs: compact-codec: - runs-on: - group: Reth + runs-on: ubuntu-latest strategy: matrix: bin: - cargo run --bin reth --features "dev" - cargo run --bin op-reth --features "dev" --manifest-path crates/optimism/bin/Cargo.toml + - cargo run --bin scroll-reth --features "dev" --manifest-path crates/scroll/bin/scroll-reth/Cargo.toml steps: - uses: rui314/setup-mold@v1 - uses: dtolnay/rust-toolchain@stable @@ -33,8 +33,8 @@ cache-on-failure: true - name: Checkout base uses: actions/checkout@v4 with: - ref: ${{ github.base_ref || 'main' }} - # On `main` branch, generates test vectors and serializes them to disk using `Compact`. + ref: ${{ github.base_ref || 'scroll' }} + # On `scroll` branch, generates test vectors and serializes them to disk using `Compact`. - name: Generate compact vectors run: | ${{ matrix.bin }} -- test-vectors compact --write
diff --git reth/.github/workflows/deny.yml scroll-reth/.github/workflows/deny.yml new file mode 100644 index 0000000000000000000000000000000000000000..6908a3d5a56117cac1a581d25953fa4670ba45cd --- /dev/null +++ scroll-reth/.github/workflows/deny.yml @@ -0,0 +1,27 @@ +# Runs `cargo-deny` when modifying `Cargo.lock`. + +name: deny + +on: + push: + branches: [main, scroll] + paths: [Cargo.lock] + pull_request: + branches: [main, scroll] + paths: [Cargo.lock] + merge_group: + +env: + CARGO_TERM_COLOR: always + +concurrency: deny-${{ github.head_ref || github.run_id }} + +jobs: + deny: + name: deny + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: EmbarkStudios/cargo-deny-action@v2 + with: + command: check all
diff --git reth/.github/workflows/e2e.yml scroll-reth/.github/workflows/e2e.yml index ac43d6cc84f506990de02417381c3a462df6dfd1..53199542c9cb7baf60ba51f590ec2562cdc044be 100644 --- reth/.github/workflows/e2e.yml +++ scroll-reth/.github/workflows/e2e.yml @@ -19,10 +19,10 @@ jobs: test: name: e2e-testsuite - runs-on: - group: Reth + runs-on: ubuntu-latest env: RUST_BACKTRACE: 1 + RUST_MIN_STACK: 4194304 timeout-minutes: 90 steps: - uses: actions/checkout@v4
diff --git reth/.github/workflows/hive.yml scroll-reth/.github/workflows/hive.yml index d219376bef8292dc99bceb99fe6771c46f8d0edd..a833a96e62222f8f43112ff6e72415eac7eecfb4 100644 --- reth/.github/workflows/hive.yml +++ scroll-reth/.github/workflows/hive.yml @@ -24,8 +24,7 @@ prepare-hive: if: github.repository == 'paradigmxyz/reth' timeout-minutes: 45 - runs-on: - group: Reth + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Checkout hive tests @@ -146,8 +145,7 @@ needs: - prepare-reth - prepare-hive name: run ${{ matrix.scenario.sim }}${{ matrix.scenario.limit && format(' - {0}', matrix.scenario.limit) }} - runs-on: - group: Reth + runs-on: ubuntu-latest permissions: issues: write steps: @@ -214,8 +212,7 @@ cat hivetests/workspace/logs/reth/client-*.log notify-on-error: needs: test if: failure() - runs-on: - group: Reth + runs-on: ubuntu-latest steps: - name: Slack Webhook Action uses: rtCamp/action-slack-notify@v2
diff --git reth/.github/workflows/integration.yml scroll-reth/.github/workflows/integration.yml index c59a57670545e20c680fd17afb50f1a49fe41411..2f21680a470d6cc3fbddbc29eaa00e11b09f350e 100644 --- reth/.github/workflows/integration.yml +++ scroll-reth/.github/workflows/integration.yml @@ -6,10 +6,7 @@ on: pull_request: merge_group: push: - branches: [main] - schedule: - # Run once a day at 3:00 UTC - - cron: "0 3 * * *" + branches: [main, scroll]   env: CARGO_TERM_COLOR: always @@ -22,9 +19,7 @@ jobs: test: name: test / ${{ matrix.network }} - if: github.event_name != 'schedule' - runs-on: - group: Reth + runs-on: ubuntu-latest env: RUST_BACKTRACE: 1 strategy: @@ -57,7 +52,7 @@ integration-success: name: integration success runs-on: ubuntu-latest - if: always() && github.event_name != 'schedule' + if: always() needs: [test] timeout-minutes: 30 steps:
diff --git reth/.github/workflows/lint.yml scroll-reth/.github/workflows/lint.yml index f0f82a1aba362f4f24d7ca6e59d69a25fde58947..8dbb193977a3bd45dda06b6828a8a7e3d9300bd2 100644 --- reth/.github/workflows/lint.yml +++ scroll-reth/.github/workflows/lint.yml @@ -4,7 +4,7 @@ on: pull_request: merge_group: push: - branches: [main] + branches: [main, scroll]   env: CARGO_TERM_COLOR: always @@ -20,6 +20,9 @@ include: - type: ethereum args: --workspace --lib --examples --tests --benches --locked features: "ethereum asm-keccak jemalloc jemalloc-prof min-error-logs min-warn-logs min-info-logs min-debug-logs min-trace-logs" + - type: scroll + args: --bin scroll-reth --workspace --lib --examples --tests --benches --locked + features: "asm-keccak jemalloc jemalloc-prof min-error-logs min-warn-logs min-info-logs min-debug-logs min-trace-logs" steps: - uses: actions/checkout@v4 - uses: rui314/setup-mold@v1 @@ -93,7 +96,7 @@ run: .github/assets/check_rv32imac.sh   crate-checks: runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 45 steps: - uses: actions/checkout@v4 - uses: rui314/setup-mold@v1 @@ -103,6 +106,7 @@ - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - run: cargo hack check --workspace + - run: cargo check -p scroll-reth   msrv: name: MSRV @@ -230,7 +234,7 @@ # Checks that selected rates can compile with power set of features features: name: features runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 60 steps: - uses: actions/checkout@v4 - uses: rui314/setup-mold@v1 @@ -263,6 +267,23 @@ deny: uses: ithacaxyz/ci/.github/workflows/deny.yml@main   + openvm: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2025-07-30 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - run: cargo install --locked --git https://github.com/openvm-org/openvm.git --tag v1.4.0-rc.2 cargo-openvm + - name: verify openvm compatibility + env: + OPENVM_RUST_TOOLCHAIN: nightly-2025-07-30 + run: cargo openvm build --manifest-path crates/scroll/openvm-compat/Cargo.toml + lint-success: name: lint success runs-on: ubuntu-latest @@ -282,6 +303,7 @@ - no-test-deps - features - feature-propagation - deny + - openvm timeout-minutes: 30 steps: - name: Decide whether the needed jobs succeeded or failed
diff --git reth/.github/workflows/pages.yaml scroll-reth/.github/workflows/pages.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5717d4c95543c09d59a7d51803826ead2641a5c5 --- /dev/null +++ scroll-reth/.github/workflows/pages.yaml @@ -0,0 +1,33 @@ +name: Build and publish forkdiff github-pages +permissions: + contents: write +on: + push: + branches: + - scroll +jobs: + deploy: + concurrency: ci-${{ github.ref }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 # make sure to fetch the old commit we diff against + + - name: Build forkdiff + uses: "docker://protolambda/forkdiff:0.1.0" + with: + args: -repo=/github/workspace -fork=/github/workspace/fork.yaml -out=/github/workspace/index.html + + - name: Build pages + run: | + mkdir -p tmp/pages + mv index.html tmp/pages/index.html + touch tmp/pages/.nojekyll + + - name: Deploy + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: tmp/pages + clean: true \ No newline at end of file
diff --git reth/.github/workflows/prepare-reth.yml scroll-reth/.github/workflows/prepare-reth.yml index 422eba19d16738c729bbd73aad7a592537842af0..9b04eea2f706c107191e94d66f7ae0219dcef815 100644 --- reth/.github/workflows/prepare-reth.yml +++ scroll-reth/.github/workflows/prepare-reth.yml @@ -26,8 +26,7 @@ jobs: prepare-reth: if: github.repository == 'paradigmxyz/reth' timeout-minutes: 45 - runs-on: - group: Reth + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: mkdir artifacts
diff --git reth/.github/workflows/release.yml scroll-reth/.github/workflows/release.yml index 9acc024baf1ac3a0571195b0926e18ba3dccfb4e..8d3cfc950cc7688cfbf8043cad3204aa87fb4c73 100644 --- reth/.github/workflows/release.yml +++ scroll-reth/.github/workflows/release.yml @@ -285,4 +285,4 @@ echo "- Docker images would be pushed to registry" echo "- A draft release would be created" echo "" echo "### Next Steps" - echo "To perform a real release, push a git tag." + echo "To perform a real release, push a git tag." \ No newline at end of file
diff --git reth/.github/workflows/stage.yml scroll-reth/.github/workflows/stage.yml index 5c3262827626c52a94a993a4a7ea55acdfd60e1e..95d784257a56ab0a915090773691080530b47387 100644 --- reth/.github/workflows/stage.yml +++ scroll-reth/.github/workflows/stage.yml @@ -6,7 +6,7 @@ on: pull_request: merge_group: push: - branches: [main] + branches: [ main, scroll ]   env: CARGO_TERM_COLOR: always @@ -22,8 +22,7 @@ stage: name: stage-run-test # Only run stage commands test in merge groups if: github.event_name == 'merge_group' - runs-on: - group: Reth + runs-on: ubuntu-latest env: RUST_LOG: info,sync=error RUST_BACKTRACE: 1
diff --git reth/.github/workflows/stale.yml scroll-reth/.github/workflows/stale.yml index 38cca2fb1a9b895f2c548d9db60678c3d6bea241..0d51a0e4eae60c0a8e5a7383eb134b85966b638a 100644 --- reth/.github/workflows/stale.yml +++ scroll-reth/.github/workflows/stale.yml @@ -4,8 +4,6 @@ name: stale issues   on: workflow_dispatch: {} - schedule: - - cron: "30 1 * * *"   jobs: close-issues:
diff --git reth/.github/workflows/sync-era.yml scroll-reth/.github/workflows/sync-era.yml index 11d2baa9994ea11c21c884f77047e71e72efcbda..808f1ebc4ae4a89f3c79684a43f9d902f09c9800 100644 --- reth/.github/workflows/sync-era.yml +++ scroll-reth/.github/workflows/sync-era.yml @@ -17,8 +17,7 @@ jobs: sync: name: sync (${{ matrix.chain.bin }}) - runs-on: - group: Reth + runs-on: ubuntu-latest env: RUST_LOG: info,sync=error RUST_BACKTRACE: 1 @@ -64,4 +63,4 @@ run: | ${{ matrix.chain.bin }} stage unwind num-blocks 100 --chain ${{ matrix.chain.chain }} - name: Run stage unwind to block hash run: | - ${{ matrix.chain.bin }} stage unwind to-block ${{ matrix.chain.unwind-target }} --chain ${{ matrix.chain.chain }} + ${{ matrix.chain.bin }} stage unwind to-block ${{ matrix.chain.unwind-target }} --chain ${{ matrix.chain.chain }}
diff --git reth/.github/workflows/sync.yml scroll-reth/.github/workflows/sync.yml index c19ec73ea9bd18cbaf3d3489cf95a12d5c2c1d62..e7d1c93f18b544744d6ede275cecf38a4938f91d 100644 --- reth/.github/workflows/sync.yml +++ scroll-reth/.github/workflows/sync.yml @@ -3,9 +3,9 @@ name: sync test   on: - workflow_dispatch: - schedule: - - cron: "0 */6 * * *" + merge_group: + push: + branches: [main, scroll]   env: CARGO_TERM_COLOR: always @@ -17,8 +17,7 @@ jobs: sync: name: sync (${{ matrix.chain.bin }}) - runs-on: - group: Reth + runs-on: ubuntu-latest env: RUST_LOG: info,sync=error RUST_BACKTRACE: 1 @@ -38,6 +37,12 @@ chain: base tip: "0xbb9b85352c7ebca6ba8efc63bd66cecd038c92ec8ebd02e153a3e0b197e672b7" block: 10000 unwind-target: "0x118a6e922a8c6cab221fc5adfe5056d2b72d58c6580e9c5629de55299e2cf8de" + - build: install-scroll + bin: scroll-reth + chain: scroll-mainnet + tip: "0x1f398ce1e03b9d7d7fcba8512dc43c9f84ecaffb15954ab178fab48151e84484" + block: 50000 + unwind-target: "0xc434910471ff41b3f097360b8c5d20459018023833081192dbe490a12ae2937f" steps: - uses: actions/checkout@v4 - uses: rui314/setup-mold@v1 @@ -53,6 +58,7 @@ ${{ matrix.chain.bin }} node \ --chain ${{ matrix.chain.chain }} \ --debug.tip ${{ matrix.chain.tip }} \ --debug.max-block ${{ matrix.chain.block }} \ + --builder.gaslimit 20000000 \ --debug.terminate - name: Verify the target block hash run: |
diff --git reth/.github/workflows/unit.yml scroll-reth/.github/workflows/unit.yml index ffdf38dc9f7e6192e1a10b75f739cd55180d1b7f..cb317b7ed86986e49b2e74e2bcd051df252eb29c 100644 --- reth/.github/workflows/unit.yml +++ scroll-reth/.github/workflows/unit.yml @@ -6,7 +6,7 @@ on: pull_request: merge_group: push: - branches: [main] + branches: [main, scroll]   env: CARGO_TERM_COLOR: always @@ -20,7 +20,7 @@ jobs: test: name: test / ${{ matrix.type }} (${{ matrix.partition }}/${{ matrix.total_partitions }}) runs-on: - group: Reth + group: scroll-reth-runner-group env: RUST_BACKTRACE: 1 strategy: @@ -42,8 +42,14 @@ - type: optimism args: --features "asm-keccak" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum" partition: 2 total_partitions: 2 + - type: scroll + args: -p "reth-scroll-*" -p "scroll-alloy-*" --locked + partition: 1 + total_partitions: 1 timeout-minutes: 30 steps: + - name: Free up disk space + run: rm -rf /opt/hostedtoolcache - uses: actions/checkout@v4 - uses: rui314/setup-mold@v1 - uses: dtolnay/rust-toolchain@stable @@ -60,18 +66,19 @@ run: | cargo nextest run \ ${{ matrix.args }} --workspace \ --exclude ef-tests --no-tests=warn \ - --partition hash:${{ matrix.partition }}/2 \ + --partition hash:${{ matrix.partition }}/${{ matrix.total_partitions }} \ -E "!kind(test) and not binary(e2e_testsuite)"   state: name: Ethereum state tests - runs-on: - group: Reth + runs-on: ubuntu-latest env: RUST_LOG: info,sync=error RUST_BACKTRACE: 1 timeout-minutes: 30 steps: + - name: Free up disk space + run: rm -rf /opt/hostedtoolcache - uses: actions/checkout@v4 - name: Checkout ethereum/tests uses: actions/checkout@v4 @@ -91,8 +98,7 @@ - run: cargo nextest run --release -p ef-tests --features "asm-keccak ef-tests"   doc: name: doc tests - runs-on: - group: Reth + runs-on: ubuntu-latest env: RUST_BACKTRACE: 1 timeout-minutes: 30
diff --git reth/fork.yaml scroll-reth/fork.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a13287f09dfc8baa56d2e5e18faecb7c4574a729 --- /dev/null +++ scroll-reth/fork.yaml @@ -0,0 +1,42 @@ +title: "scroll-reth - reth fork diff overview" +footer: | + Fork-diff overview of [`scroll-reth`](https://github.com/scroll-tech/reth), a fork of [`reth`](https://github.com/paradigmxyz/reth). +base: + name: reth + url: https://github.com/paradigmxyz/reth + hash: bcbd2d64cedbde0c8f4f044a74da1bfb9324e2ce +fork: + name: scroll-reth + url: https://github.com/scroll-tech/reth + ref: refs/heads/scroll +def: + title: "scroll-reth" + description: | + This is an overview of the changes in [`scroll-reth`](https://github.com/scroll-tech/reth), + a fork of [`reth`](https://github.com/paradigmxyz/reth). + sub: + - title: "crates/scroll" + globs: + - "crates/scroll/**/*" + - title: "crates/ethereum" + globs: + - "crates/ethereum/**/*" + - title: "crates/optimism" + globs: + - "crates/optimism/**/*" + - title: "crates/primitives" + globs: + - "crates/primitives*/**/*" + - title: "crates/stages" + globs: + - "crates/stages/**/*" + - title: "crates/storage" + globs: + - "crates/storage/**/*" + - title: "crates/trie" + globs: + - "crates/trie/**/*" + +ignore: + - "fork.yaml" + - ".github/**"