scroll-reth

diff: ignored:
+16648
-153
+168
-45

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..6c45faeee8f66434c2e924442762105804717767 --- /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"] } + +# 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 = [ + "reth-codec", + "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", +] +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 = [ + "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..1f3d755906a8d3f51acda2a76474cac77f67a682 --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/lib.rs @@ -0,0 +1,23 @@ +#![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::{ + ScrollL1MessageTransactionFields, ScrollPooledTransaction, 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;
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..dfae2bdb314d4456aa698e0c1369f24e86b975f1 --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/receipt/envelope.rs @@ -0,0 +1,353 @@ +//! 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 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::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::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::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::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::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::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::L1Message(t) | Self::Legacy(t) | Self::Eip2930(t) | Self::Eip1559(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::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::Eip1559 => Ok(Self::Eip1559(Decodable::decode(buf)?)), + ScrollTxType::Eip2930 => Ok(Self::Eip2930(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..ae7cae47d55c60dbfb7fd6df130659eec1d35237 --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/transaction/envelope.rs @@ -0,0 +1,558 @@ +use alloy_consensus::{ + transaction::RlpEcdsaDecodableTx, Sealable, Sealed, Signed, Transaction, TxEip1559, TxEip2930, + TxLegacy, Typed2718, +}; +use alloy_eips::{ + eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718}, + eip2930::AccessList, + eip7702::SignedAuthorization, +}; +use alloy_primitives::{Address, Bytes, TxKind, B256, U256}; +use alloy_rlp::{Decodable, Encodable}; + +use crate::{ScrollTxType, TxL1Message}; + +/// 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)] +#[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))] +#[non_exhaustive] +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>), + /// 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<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 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::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::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::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::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::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::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::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::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::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::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::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::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::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::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::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::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::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::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::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 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 [`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::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::L1Message(t) => t.eip2718_encoded_length(), + } + } +} + +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::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::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::L1Message(tx) => { + tx.eip2718_encode(out); + } + } + } + + fn trie_hash(&self) -> B256 { + match self { + Self::Legacy(tx) => *tx.hash(), + Self::Eip1559(tx) => *tx.hash(), + Self::Eip2930(tx) => *tx.hash(), + Self::L1Message(tx) => tx.seal(), + } + } +} + +#[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 = "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::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::L1Message(tx) => Self::L1Message(tx), + } + } + } +} + +#[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..40eb8e4a121b442fa4ff7fcdbeb925d130022c74 --- /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. + 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> + 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/mod.rs scroll-reth/crates/scroll/alloy/consensus/src/transaction/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..ce98f42336fb9319f13163f5832c3dcf81ea033b --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/transaction/mod.rs @@ -0,0 +1,19 @@ +//! Transaction types for Scroll. + +mod tx_type; +pub use tx_type::{ScrollTxType, L1_MESSAGE_TX_TYPE_ID}; + +mod envelope; +pub use envelope::ScrollTxEnvelope; + +mod l1_message; +pub use l1_message::{ScrollL1MessageTransactionFields, TxL1Message, L1_MESSAGE_TRANSACTION_TYPE}; + +mod typed; +pub use typed::ScrollTypedTransaction; + +mod pooled; +pub use pooled::ScrollPooledTransaction; + +#[cfg(feature = "serde")] +pub use l1_message::serde_l1_message_tx_rpc;
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..1955690cbf94eedca72e1478e426dbe62f8d29dc --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/transaction/pooled.rs @@ -0,0 +1,478 @@ +//! 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::{ + transaction::{RlpEcdsaDecodableTx, TxEip1559, TxEip2930, TxLegacy}, + SignableTransaction, Signed, Transaction, 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>), +} + +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(), + } + } + + /// 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(), + } + } + + /// 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(), + } + } + + /// 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(), + } + } + + /// 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), + } + } + + /// 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(), + } + } + + /// 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(), + } + } + + /// 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, + } + } +} + +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 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), + } + } + + 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(), + } + } + + 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), + } + } + + 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::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(), + } + } + + fn nonce(&self) -> u64 { + match self { + Self::Legacy(tx) => tx.tx().nonce(), + Self::Eip2930(tx) => tx.tx().nonce(), + Self::Eip1559(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(), + } + } + + 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(), + } + } + + 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(), + } + } + + 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(), + } + } + + 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(), + } + } + + 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(), + } + } + + 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), + } + } + + 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(), + } + } + + fn kind(&self) -> TxKind { + match self { + Self::Legacy(tx) => tx.tx().kind(), + Self::Eip2930(tx) => tx.tx().kind(), + Self::Eip1559(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(), + } + } + + fn value(&self) -> U256 { + match self { + Self::Legacy(tx) => tx.tx().value(), + Self::Eip2930(tx) => tx.tx().value(), + Self::Eip1559(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(), + } + } + + 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(), + } + } + + 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(), + } + } + + 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(), + } + } +} + +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(), + } + } +} + +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() + } +} + +#[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..71d746b656f7d1d996a82d9861e62107e7e43a03 --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/transaction/tx_type.rs @@ -0,0 +1,200 @@ +//! 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, + /// L1 message transaction type. + #[display("l1_message")] + L1Message = L1_MESSAGE_TX_TYPE_ID, +} + +impl ScrollTxType { + /// List of all variants. + pub const ALL: [Self; 4] = [Self::Legacy, Self::Eip1559, Self::Eip2930, 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::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::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 { + 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(), 4); + let all = vec![ + ScrollTxType::Legacy, + ScrollTxType::Eip1559, + ScrollTxType::Eip2930, + 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..03ba72a7ca8f0ff06c0a4c9c4834a494385056f5 --- /dev/null +++ scroll-reth/crates/scroll/alloy/consensus/src/transaction/typed.rs @@ -0,0 +1,425 @@ +use crate::{ScrollTxEnvelope, ScrollTxType, TxL1Message}; +use alloy_consensus::{ + SignableTransaction, Transaction, TxEip1559, TxEip2930, TxLegacy, Typed2718, +}; +use alloy_eips::eip2930::AccessList; +use alloy_primitives::{Address, Bytes, 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. `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), + /// 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<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::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::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 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::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::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::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::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::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::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::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::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::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::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::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::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::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::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::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::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::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::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::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::L1Message(tx) => tx.is_create(), + } + } +} + +#[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::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::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), + /// `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::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::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..42b753d4f7a9fc0c1dfe7e09eacbc9333706ea45 --- /dev/null +++ scroll-reth/crates/scroll/alloy/evm/Cargo.toml @@ -0,0 +1,59 @@ +[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 } + +[dev-dependencies] +eyre.workspace = true + +[features] +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", +] +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", +]
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..61d873bac532d05e869a97d92510ce1a7c9233fe --- /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().clone(), bytecode); + + 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..9e7520cd3553cacfc3b4f9bea4628108d4ab862f --- /dev/null +++ scroll-reth/crates/scroll/alloy/evm/src/block/mod.rs @@ -0,0 +1,315 @@ +pub mod curie; + +pub use receipt_builder::{ReceiptBuilderCtx, ScrollReceiptBuilder}; +mod receipt_builder; + +use crate::{ + block::curie::{apply_curie_hard_fork, L1_GAS_PRICE_ORACLE_ADDRESS}, + ScrollEvm, ScrollEvmFactory, ScrollTransactionIntoTxEnv, +}; +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, ExecutableTx, OnStateHook, + }, + Database, Evm, FromRecoveredTx, FromTxWithEncoded, +}; +use alloy_primitives::{B256, U256}; +use revm::{ + context::{ + result::{InvalidTransaction, ResultAndState}, + TxEnv, + }, + database::State, + DatabaseCommit, Inspector, +}; +use revm_scroll::builder::ScrollContext; +use scroll_alloy_consensus::L1_MESSAGE_TRANSACTION_TYPE; +use scroll_alloy_hardforks::{ScrollHardfork, ScrollHardforks}; + +/// Context for Scroll Block Execution. +#[derive(Debug, 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, + /// Receipts of executed transactions. + receipts: Vec<R::Receipt>, + /// Total gas used by executed transactions. + gas_used: u64, +} + +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 const fn new(evm: E, spec: Spec, receipt_builder: R) -> Self { + Self { evm, spec, receipt_builder, receipts: Vec::new(), gas_used: 0 } + } +} + +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); + 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)?; + + if self + .spec + .scroll_fork_activation(ScrollHardfork::Curie) + .transitions_at_block(self.evm.block().number) + { + if let Err(err) = apply_curie_hard_fork(self.evm.db_mut()) { + return Err(BlockExecutionError::msg(format!( + "error occurred at Curie fork: {err:?}" + ))); + }; + } + + Ok(()) + } + + fn execute_transaction_with_result_closure( + &mut self, + tx: impl ExecutableTx<Self>, + f: impl FnOnce(&revm::context::result::ExecutionResult<<Self::Evm as Evm>::HaltReason>), + ) -> Result<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(); + + // verify the transaction type is accepted by the current fork. + if tx.tx().is_eip2930() && !chain_spec.is_curie_active_at_block(self.evm.block().number) { + return Err(BlockValidationError::InvalidTx { + hash, + error: Box::new(InvalidTransaction::Eip2930NotSupported), + } + .into()) + } + if tx.tx().is_eip1559() && !chain_spec.is_curie_active_at_block(self.evm.block().number) { + 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() { + 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))?; + + f(&result); + + 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(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> EvmExt for ScrollEvm<DB, I> +where + DB: Database, + I: Inspector<ScrollContext<DB>>, +{ + 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()?; + Some(l1_block_info.calculate_tx_l1_cost(transaction_rlp_bytes, self.ctx().cfg.spec)) + } +} + +/// Scroll block executor factory. +#[derive(Debug, Clone, Default, Copy)] +pub struct ScrollBlockExecutorFactory<R, Spec = ScrollHardfork, EvmFactory = ScrollEvmFactory> { + /// Receipt builder. + receipt_builder: R, + /// Chain specification. + spec: Spec, + /// EVM factory. + evm_factory: EvmFactory, +} + +impl<R, Spec, EvmFactory> ScrollBlockExecutorFactory<R, Spec, EvmFactory> { + /// Creates a new [`ScrollBlockExecutorFactory`] with the given receipt builder, spec and + /// factory. + pub const fn new(receipt_builder: R, spec: Spec, evm_factory: EvmFactory) -> 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) -> &EvmFactory { + &self.evm_factory + } +} + +impl<R, Spec> BlockExecutorFactory for ScrollBlockExecutorFactory<R, Spec> +where + R: ScrollReceiptBuilder<Transaction: Transaction + Encodable2718, Receipt: TxReceipt>, + Spec: ScrollHardforks, + ScrollTransactionIntoTxEnv<TxEnv>: + FromRecoveredTx<R::Transaction> + FromTxWithEncoded<R::Transaction>, + Self: 'static, +{ + type EvmFactory = ScrollEvmFactory; + 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: ScrollEvm<&'a mut State<DB>, I>, + _ctx: Self::ExecutionCtx<'a>, + ) -> impl BlockExecutorFor<'a, Self, DB, I> + where + DB: Database + 'a, + I: Inspector<ScrollContext<&'a mut State<DB>>> + 'a, + { + ScrollBlockExecutor::new(evm, &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..68226f8bb2a3af74c0e7176c9cdf81809dfe9183 --- /dev/null +++ scroll-reth/crates/scroll/alloy/evm/src/lib.rs @@ -0,0 +1,230 @@ +//! 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)] + +pub use block::{ + curie, EvmExt, ReceiptBuilderCtx, ScrollBlockExecutionCtx, ScrollBlockExecutor, + ScrollBlockExecutorFactory, ScrollReceiptBuilder, +}; +mod block; + +pub use tx::ScrollTransactionIntoTxEnv; +mod tx; + +extern crate alloc; + +use alloc::vec::Vec; +use alloy_evm::{Database, Evm, EvmEnv, EvmFactory}; +use alloy_primitives::{Address, Bytes, TxKind, U256}; +use core::{ + fmt::Debug, + 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, +}; +use revm_scroll::{ + builder::{DefaultScrollContext, ScrollBuilder, ScrollContext}, + instructions::ScrollInstructions, + precompile::ScrollPrecompileProvider, + ScrollSpecId, ScrollTransaction, +}; + +/// 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> { + /// Provides a reference to the EVM context. + pub const fn ctx(&self) -> &ScrollContext<DB> { + &self.inner.0.data.ctx + } + + /// Provides a mutable reference to the EVM context. + pub const fn ctx_mut(&mut self) -> &mut ScrollContext<DB> { + &mut self.inner.0.data.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; + + 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.set_tx(tx.into()); + self.inner.inspect_replay() + } else { + self.inner.transact(tx.into()) + } + } + + fn transact_system_call( + &mut self, + caller: Address, + contract: Address, + data: Bytes, + ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> { + let tx = ScrollTransaction { + base: TxEnv { + caller, + kind: TxKind::Call(contract), + // Explicitly set nonce to 0 so revm does not do any nonce checks + nonce: 0, + gas_limit: 30_000_000, + value: U256::ZERO, + data, + // Setting the gas price to zero enforces that no value is transferred as part of + // the call, and that the call will not count against the block's + // gas limit + gas_price: 0, + // The chain ID check is not relevant here and is disabled if set to None + chain_id: None, + // Setting the gas priority fee to None ensures the effective gas price is derived + // from the `gas_price` field, which we need to be zero + gas_priority_fee: None, + access_list: Default::default(), + // blob fields can be None for this tx + blob_hashes: Vec::new(), + max_fee_per_blob_gas: 0, + tx_type: 0, + authorization_list: Default::default(), + }, + rlp_bytes: Some(Default::default()), + }; + + let mut gas_limit = tx.base.gas_limit; + let mut basefee = 0; + let mut disable_nonce_check = true; + + // ensure the block gas limit is >= the tx + core::mem::swap(&mut self.block.gas_limit, &mut gas_limit); + // disable the base fee check for this call by setting the base fee to zero + core::mem::swap(&mut self.block.basefee, &mut basefee); + // disable the nonce check + core::mem::swap(&mut self.cfg.disable_nonce_check, &mut disable_nonce_check); + + let res = self.transact(ScrollTransactionIntoTxEnv::from(tx)); + + // swap back to the previous gas limit + core::mem::swap(&mut self.block.gas_limit, &mut gas_limit); + // swap back to the previous base fee + core::mem::swap(&mut self.block.basefee, &mut basefee); + // swap back to the previous nonce check flag + core::mem::swap(&mut self.cfg.disable_nonce_check, &mut disable_nonce_check); + + res + } + + 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.data.ctx; + + (journaled_state.database, EvmEnv { block_env, cfg_env }) + } + + fn set_inspector_enabled(&mut self, enabled: bool) { + self.inspect = enabled; + } +} + +/// Factory producing [`ScrollEvm`]s. +#[derive(Debug, Default, Clone, Copy)] +#[non_exhaustive] +pub struct ScrollEvmFactory; + +impl EvmFactory for ScrollEvmFactory { + type Evm<DB: Database, I: Inspector<ScrollContext<DB>>> = ScrollEvm<DB, I>; + 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; + + fn create_evm<DB: Database>( + &self, + db: DB, + input: EvmEnv<ScrollSpecId>, + ) -> Self::Evm<DB, NoOpInspector> { + ScrollEvm { + inner: Context::scroll() + .with_db(db) + .with_block(input.block_env) + .with_cfg(input.cfg_env) + .build_scroll_with_inspector(NoOpInspector {}), + 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> { + ScrollEvm { + inner: Context::scroll() + .with_db(db) + .with_block(input.block_env) + .with_cfg(input.cfg_env) + .build_scroll_with_inspector(inspector), + inspect: true, + } + } +}
diff --git reth/crates/scroll/alloy/evm/src/tx.rs scroll-reth/crates/scroll/alloy/evm/src/tx.rs new file mode 100644 index 0000000000000000000000000000000000000000..b463890a0a7d87661cad22d9d823514d16b0b692 --- /dev/null +++ scroll-reth/crates/scroll/alloy/evm/src/tx.rs @@ -0,0 +1,119 @@ +use alloy_evm::IntoTxEnv; +use alloy_primitives::{Address, Bytes, TxKind, B256, U256}; +use core::ops::{Deref, DerefMut}; +use revm::context::Transaction; +use revm_scroll::ScrollTransaction; + +/// 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>) -> Self { + Self(ScrollTransaction::new(base, rlp_bytes)) + } +} + +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 = T::AccessListItem; + type Authorization = T::Authorization; + + 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() + } +}
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..bde5ff119947d332cbcc18ea1f7fe892f6e02437 --- /dev/null +++ scroll-reth/crates/scroll/alloy/hardforks/src/hardfork.rs @@ -0,0 +1,81 @@ +//! 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 + } +); + +impl ScrollHardfork { + /// Scroll mainnet list of hardforks. + pub const fn scroll_mainnet() -> [(Self, ForkCondition); 7] { + [ + (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)), + ] + } + + /// Scroll sepolia list of hardforks. + pub const fn scroll_sepolia() -> [(Self, ForkCondition); 7] { + [ + (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)), + ] + } +} + +#[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"]; + let expected_hardforks = [ + ScrollHardfork::Bernoulli, + ScrollHardfork::Curie, + ScrollHardfork::Darwin, + ScrollHardfork::DarwinV2, + ScrollHardfork::Euclid, + ScrollHardfork::EuclidV2, + ]; + + 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..846fcabf176e8b4ee03984fd48c6228a171b2b76 --- /dev/null +++ scroll-reth/crates/scroll/alloy/hardforks/src/lib.rs @@ -0,0 +1,99 @@ +//! 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) + } +} + +/// 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..ae1ada97ade9cf5710d59b5b8519b2638e13c00a --- /dev/null +++ scroll-reth/crates/scroll/alloy/network/Cargo.toml @@ -0,0 +1,23 @@ +[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] +scroll-alloy-consensus.workspace = true +scroll-alloy-rpc-types.workspace = true + +# alloy +alloy-consensus.workspace = true +alloy-network.workspace = true +alloy-primitives.workspace = true +alloy-rpc-types-eth.workspace = true +alloy-signer.workspace = true
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..e0a171d82e04b43b29c476a7f15d0aea9a584cb2 --- /dev/null +++ scroll-reth/crates/scroll/alloy/network/src/lib.rs @@ -0,0 +1,228 @@ +#![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_rpc_types_eth::AccessList; +use scroll_alloy_consensus::{self, ScrollTxEnvelope, ScrollTxType, ScrollTypedTransaction}; +use scroll_alloy_rpc_types::ScrollTransactionRequest; + +/// Types for an 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 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 => unimplemented!(), + } + } + + #[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 => unimplemented!(), + }) + } + + 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::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::Legacy(tx) => ScrollTxEnvelope::Legacy(tx), + _ => unreachable!(), + }) + } +}
diff --git reth/crates/scroll/alloy/provider/Cargo.toml scroll-reth/crates/scroll/alloy/provider/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..154599dba599882000fe3b47687ed9275ff9e008 --- /dev/null +++ scroll-reth/crates/scroll/alloy/provider/Cargo.toml @@ -0,0 +1,79 @@ +[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-json-rpc.workspace = true +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 +jsonrpsee-types.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", + "reth-scroll-payload/std", + "futures-util/std", + "reth-scroll-chainspec/std", + "thiserror/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..d2d038e500849e66b508499e504a295692339ace --- /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..5021b96c76cd8e267afc52890181325232388ebf --- /dev/null +++ scroll-reth/crates/scroll/alloy/provider/src/engine/provider.rs @@ -0,0 +1,196 @@ +use super::{ScrollEngineApi, ScrollEngineApiResult}; +use alloy_primitives::{bytes::Bytes, BlockHash, U64}; +use alloy_provider::{Network, 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 derive_more::Deref; +use http_body_util::Full; +use reqwest::Url; +use scroll_alloy_network::Scroll; +use scroll_alloy_rpc_types_engine::ScrollPayloadAttributes; + +/// An authenticated [`alloy_provider::Provider`] to the [`ScrollEngineApi`]. +#[derive(Debug, Clone, Deref)] +pub struct ScrollAuthEngineApiProvider<N: Network = Scroll> { + auth_provider: RootProvider<N>, +} + +impl ScrollAuthEngineApiProvider { + /// 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::new(client); + Self { auth_provider: provider } + } +} + +#[async_trait::async_trait] +impl ScrollEngineApi for ScrollAuthEngineApiProvider { + async fn new_payload_v1( + &self, + payload: ExecutionPayloadV1, + ) -> ScrollEngineApiResult<PayloadStatus> { + Ok(self.auth_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 + .client() + .request("engine_forkchoiceUpdatedV1", (fork_choice_state, payload_attributes)) + .await?) + } + + async fn get_payload_v1( + &self, + payload_id: PayloadId, + ) -> ScrollEngineApiResult<ExecutionPayloadV1> { + Ok(self.client().request("engine_getPayloadV1", (payload_id,)).await?) + } + + async fn get_payload_bodies_by_hash_v1( + &self, + block_hashes: Vec<BlockHash>, + ) -> ScrollEngineApiResult<ExecutionPayloadBodiesV1> { + Ok(self.client().request("engine_getPayloadBodiesByHashV1", (block_hashes,)).await?) + } + + async fn get_payload_bodies_by_range_v1( + &self, + start: U64, + count: U64, + ) -> ScrollEngineApiResult<ExecutionPayloadBodiesV1> { + Ok(self.client().request("engine_getPayloadBodiesByRangeV1", (start, count)).await?) + } + + async fn get_client_version_v1( + &self, + client_version: ClientVersionV1, + ) -> ScrollEngineApiResult<Vec<ClientVersionV1>> { + Ok(self.client().request("engine_getClientVersionV1", (client_version,)).await?) + } + + async fn exchange_capabilities( + &self, + capabilities: Vec<String>, + ) -> ScrollEngineApiResult<Vec<String>> { + Ok(self.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..83d0a3e74538e6a148a8643d19b890cb3072cc1c --- /dev/null +++ scroll-reth/crates/scroll/alloy/rpc-types-engine/Cargo.toml @@ -0,0 +1,44 @@ +[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] +serde_json.workspace = true + +[features] +default = ["std"] +arbitrary = [ + "dep:arbitrary", + "alloy-primitives/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..80ff2f203b09ee5f6ffedb36bf06448d1f545dfb --- /dev/null +++ scroll-reth/crates/scroll/alloy/rpc-types-engine/src/attributes.rs @@ -0,0 +1,88 @@ +//! Scroll-specific payload attributes. + +use alloc::vec::Vec; +use alloy_primitives::{Bytes, 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 pre-Euclid block data hint, necessary for the block builder to derive the correct block + /// hash. + pub block_data_hint: Option<BlockDataHint>, +} + +/// 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 extra data for the block. + pub extra_data: Bytes, + /// The difficulty for the block. + pub difficulty: U256, +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for BlockDataHint { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> { + Ok(Self { extra_data: Bytes::arbitrary(u)?, difficulty: 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: Some(BlockDataHint::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: Some(BlockDataHint { + extra_data: b"world".into(), + difficulty: U256::from(10), + }), + }; + + 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..63d1d5e06e98f31c97fc7ccc75b0992234ac1d9b --- /dev/null +++ scroll-reth/crates/scroll/alloy/rpc-types/src/transaction.rs @@ -0,0 +1,284 @@ +//! Scroll specific types related to transactions. + +use alloy_consensus::{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::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 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..438b6e83a3bbc09b9d4e94be62ff0374593cfb89 --- /dev/null +++ scroll-reth/crates/scroll/alloy/rpc-types/src/transaction/request.rs @@ -0,0 +1,182 @@ +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::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::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..8ca0487033fd720d87f35e98d8876abeb464133d --- /dev/null +++ scroll-reth/crates/scroll/bin/scroll-reth/Cargo.toml @@ -0,0 +1,38 @@ +[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 +reth-engine-local = { workspace = true, features = ["scroll-alloy-traits"] } +reth-provider.workspace = true + +# scroll +reth-scroll-cli.workspace = true +reth-scroll-node.workspace = true + +# misc +clap = { workspace = true, features = ["derive", "env"] } +tracing.workspace = true + +[features] +skip-state-root-validation = [ + "reth-scroll-node/skip-state-root-validation", + "reth-provider/skip-state-root-validation", +] +dev = ["reth-scroll-cli/dev"] + +[[bin]] +name = "scroll-reth" +path = "src/main.rs" +required-features = ["skip-state-root-validation"]
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..cddbce9da50aaa7b06f618389bd2548e41cb5367 --- /dev/null +++ scroll-reth/crates/scroll/bin/scroll-reth/src/main.rs @@ -0,0 +1,29 @@ +//! 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, ScrollRollupArgs}; + use reth_scroll_node::ScrollNode; + 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::<_, _, ScrollNode>(|builder, _| async move { + info!(target: "reth::cli", "Launching node"); + let handle = builder.launch_node(ScrollNode).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..f62e7b8e5d1baaed5413fb885eee9884af22dfa4 --- /dev/null +++ scroll-reth/crates/scroll/chainspec/Cargo.toml @@ -0,0 +1,66 @@ +[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, features = ["scroll"] } +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 +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..7f4fc2d48ca32b44ef59b534d92162fd6d9299de --- /dev/null +++ scroll-reth/crates/scroll/chainspec/res/genesis/dev.json @@ -0,0 +1,86 @@ +{ + "nonce": "0x0", + "timestamp": "0x6490fdd2", + "extraData": "0x", + "gasLimit": "0x1c9c380", + "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" +} \ No newline at end of file
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..458e4ad51638da1301d106566369ab507303bf70 --- /dev/null +++ scroll-reth/crates/scroll/chainspec/src/constants.rs @@ -0,0 +1,74 @@ +use crate::genesis::L1Config; +use alloy_primitives::{address, b256, Address, B256}; + +/// The transaction fee recipient on the L2. +pub const SCROLL_FEE_VAULT_ADDRESS: Address = address!("5300000000000000000000000000000000000005"); + +/// 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 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, + 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 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 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, + scroll_chain_address: SCROLL_SEPOLIA_L1_PROXY_ADDRESS, + num_l1_messages_per_block: SCROLL_SEPOLIA_MAX_L1_MESSAGES, +}; + +/// The L1 message queue address for Scroll dev. +pub const SCROLL_DEV_L1_MESSAGE_QUEUE_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, + scroll_chain_address: SCROLL_DEV_L1_PROXY_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");
diff --git reth/crates/scroll/chainspec/src/dev.rs scroll-reth/crates/scroll/chainspec/src/dev.rs new file mode 100644 index 0000000000000000000000000000000000000000..5ee23d174b684aca08a0c794dd1152f52bd1dc59 --- /dev/null +++ scroll-reth/crates/scroll/chainspec/src/dev.rs @@ -0,0 +1,33 @@ +//! Chain specification in dev mode for custom chain. + +use alloc::sync::Arc; + +use alloy_chains::Chain; +use alloy_primitives::U256; +use reth_chainspec::{BaseFeeParams, BaseFeeParamsKind, ChainSpec}; +use reth_primitives_traits::SealedHeader; +use reth_scroll_forks::DEV_HARDFORKS; + +use crate::{make_genesis_header, LazyLock, ScrollChainConfig, ScrollChainSpec}; + +/// 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(|| { + 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::Constant(BaseFeeParams::ethereum()), + ..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..9bd1a0c654a01f43fe2278a498d5e20d96ea160b --- /dev/null +++ scroll-reth/crates/scroll/chainspec/src/genesis.rs @@ -0,0 +1,225 @@ +//! Scroll types for genesis data. + +use crate::{ + constants::{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>, +} + +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 proxy contract which is responsible for Scroll rollup + /// settlement. + pub scroll_chain_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 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), + 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), + 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), 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 alloy_primitives::address; + + #[test] + fn test_extract_scroll_genesis_info() { + let genesis_info = r#" + { + "archimedesBlock": 0, + "bernoulliBlock": 10, + "curieBlock": 12, + "darwinTime": 0 + } + "#; + + 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, + } + ); + } + + #[test] + fn test_extract_scroll_chain_info() { + let chain_info_str = r#" + { + "archimedesBlock": 0, + "bernoulliBlock": 10, + "curieBlock": 12, + "darwinTime": 0, + "scroll": { + "feeVaultAddress": "0x5300000000000000000000000000000000000005", + "l1Config": { + "l1ChainId": 1, + "l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B", + "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, + }), + scroll_chain_config: ScrollChainConfig { + fee_vault_address: Some(address!("5300000000000000000000000000000000000005")), + l1_config: L1Config { + l1_chain_id: 1, + l1_message_queue_address: address!("0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B"), + 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..71dbe128f21158130e1bf99dcb8578f8e21e750a --- /dev/null +++ scroll-reth/crates/scroll/chainspec/src/lib.rs @@ -0,0 +1,700 @@ +//! 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}; +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, ChainSpec, ChainSpecBuilder, DepositContract, EthChainSpec, EthereumHardforks, + ForkFilter, ForkId, Hardforks, Head, +}; +use reth_ethereum_forks::{ + ChainHardforks, EthereumHardfork, ForkCondition, ForkFilterKey, ForkHash, Hardfork, +}; +use reth_network_peers::NodeRecord; +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::{ + SCROLL_DEV_L1_CONFIG, SCROLL_DEV_L1_MESSAGE_QUEUE_ADDRESS, SCROLL_DEV_L1_PROXY_ADDRESS, + SCROLL_DEV_MAX_L1_MESSAGES, SCROLL_FEE_VAULT_ADDRESS, SCROLL_MAINNET_GENESIS_HASH, + SCROLL_MAINNET_L1_CONFIG, SCROLL_MAINNET_L1_MESSAGE_QUEUE_ADDRESS, + SCROLL_MAINNET_L1_PROXY_ADDRESS, SCROLL_MAINNET_MAX_L1_MESSAGES, SCROLL_SEPOLIA_GENESIS_HASH, + SCROLL_SEPOLIA_L1_CONFIG, SCROLL_SEPOLIA_L1_MESSAGE_QUEUE_ADDRESS, + SCROLL_SEPOLIA_L1_PROXY_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 + } + + /// 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 } + } +} + +/// Returns the chain configuration. +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 { + // TODO(scroll): need to implement Scroll L2 formula related to https://github.com/scroll-tech/reth/issues/60 + self.inner.base_fee_params_at_block(block_number) + } + + fn base_fee_params_at_timestamp(&self, timestamp: u64) -> BaseFeeParams { + // TODO(scroll): need to implement Scroll L2 formula related to https://github.com/scroll-tech/reth/issues/60 + 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() + } +} + +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: None, + 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), + ]; + + 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: 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: 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", + "l1Config": { + "l1ChainId": 1, + "l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B", + "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", + "l1Config": { + "l1ChainId": 1, + "l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B", + "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("scroll"), + serde_json::json!({ + "feeVaultAddress": "0x5300000000000000000000000000000000000005", + "l1Config": { + "l1ChainId": 1, + "l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B", + "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(), + ]; + + 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..067ca0a5e871fcfa6546f2a0bc047ae38562ac15 --- /dev/null +++ scroll-reth/crates/scroll/chainspec/src/scroll.rs @@ -0,0 +1,32 @@ +//! Chain specification for the Scroll Mainnet network. + +use crate::{ + make_genesis_header, LazyLock, ScrollChainConfig, ScrollChainSpec, SCROLL_MAINNET_GENESIS_HASH, +}; +use alloc::sync::Arc; + +use alloy_chains::{Chain, NamedChain}; +use reth_chainspec::ChainSpec; +use reth_primitives_traits::SealedHeader; +use reth_scroll_forks::SCROLL_MAINNET_HARDFORKS; + +/// 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(), + ..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..74caaf7dd2465a8454653b7572dc452eb9855019 --- /dev/null +++ scroll-reth/crates/scroll/chainspec/src/scroll_sepolia.rs @@ -0,0 +1,32 @@ +//! Chain specification for the Scroll Sepolia testnet network. + +use crate::{ + make_genesis_header, LazyLock, ScrollChainConfig, ScrollChainSpec, SCROLL_SEPOLIA_GENESIS_HASH, +}; +use alloc::sync::Arc; + +use alloy_chains::{Chain, NamedChain}; +use reth_chainspec::ChainSpec; +use reth_primitives_traits::SealedHeader; +use reth_scroll_forks::SCROLL_SEPOLIA_HARDFORKS; + +/// 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(), + ..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..b5c79e24940217b00a3f5bd470492e7d6a212fcb --- /dev/null +++ scroll-reth/crates/scroll/cli/Cargo.toml @@ -0,0 +1,44 @@ +[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-evm.workspace = true +reth-scroll-node.workspace = true +reth-scroll-primitives.workspace = true +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/args.rs scroll-reth/crates/scroll/cli/src/args.rs new file mode 100644 index 0000000000000000000000000000000000000000..d51f83146a06b3387dbd098f63ec1e3d8d46593b --- /dev/null +++ scroll-reth/crates/scroll/cli/src/args.rs @@ -0,0 +1,3 @@ +/// Rollup arguments for the Scroll node. +#[derive(Debug, clap::Args)] +pub struct ScrollRollupArgs;
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..0690fe3a3123bf9b757ae5c6c2c68f4d1bb2f9a5 --- /dev/null +++ scroll-reth/crates/scroll/cli/src/commands/mod.rs @@ -0,0 +1,56 @@ +#[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 std::fmt; + +/// 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), +}
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..e38f4991a231aecf530f256d3abe9dff47b122ff --- /dev/null +++ scroll-reth/crates/scroll/cli/src/lib.rs @@ -0,0 +1,159 @@ +//! Scroll CLI implementation. +mod args; +pub use args::ScrollRollupArgs; + +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::{common::CliNodeTypes, node::NoArgs}; +use reth_cli_runner::CliRunner; +use reth_consensus::noop::NoopConsensus; +use reth_db::DatabaseEnv; +use reth_node_builder::{NodeBuilder, WithLaunchContext}; +use reth_node_core::{ + args::LogArgs, + version::{LONG_VERSION, SHORT_VERSION}, +}; +use reth_node_metrics::recorder::install_prometheus_recorder; +use reth_scroll_chainspec::ScrollChainSpec; +use reth_scroll_evm::ScrollExecutorProvider; +use reth_scroll_node::ScrollNetworkPrimitives; +use reth_scroll_primitives::ScrollPrimitives; +use reth_tracing::FileWorkerGuard; +use std::{ffi::OsString, fmt, future::Future, sync::Arc}; +use tracing::info; + +/// 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, +{ + /// 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, Types>(mut self, launcher: L) -> eyre::Result<()> + where + L: FnOnce(WithLaunchContext<NodeBuilder<Arc<DatabaseEnv>, C::ChainSpec>>, Ext) -> Fut, + Fut: Future<Output = eyre::Result<()>>, + Types: CliNodeTypes<ChainSpec = C::ChainSpec, Primitives = ScrollPrimitives>, + { + // add network name to logs dir + self.logs.log_file_directory = + self.logs.log_file_directory.join(self.chain.chain().to_string()); + + let _guard = self.init_tracing()?; + info!(target: "reth::cli", "Initialized tracing, debug log directory: {}", self.logs.log_file_directory); + + // Install the prometheus recorder to be sure to record all metrics + let _ = install_prometheus_recorder(); + let components = |spec: Arc<C::ChainSpec>| { + (ScrollExecutorProvider::scroll(spec), NoopConsensus::default()) + }; + + let runner = CliRunner::try_default_runtime()?; + match self.command { + Commands::Node(command) => { + runner.run_command_until_exit(|ctx| command.execute(ctx, launcher)) + } + Commands::Init(command) => runner.run_blocking_until_ctrl_c(command.execute::<Types>()), + Commands::InitState(command) => { + runner.run_blocking_until_ctrl_c(command.execute::<Types>()) + } + Commands::Import(command) => { + runner.run_blocking_until_ctrl_c(command.execute::<Types, _, _>(components)) + } + Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()), + Commands::Db(command) => runner.run_blocking_until_ctrl_c(command.execute::<Types>()), + Commands::Stage(command) => runner.run_command_until_exit(|ctx| { + command.execute::<Types, _, _, ScrollNetworkPrimitives>(ctx, components) + }), + Commands::P2P(command) => { + runner.run_until_ctrl_c(command.execute::<ScrollNetworkPrimitives>()) + } + Commands::Config(command) => runner.run_until_ctrl_c(command.execute()), + Commands::Recover(command) => { + runner.run_command_until_exit(|ctx| command.execute::<Types>(ctx)) + } + Commands::Prune(command) => runner.run_until_ctrl_c(command.execute::<Types>()), + #[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 returns a guard that must be kept alive to ensure + /// that all logs are flushed to disk. + pub fn init_tracing(&self) -> eyre::Result<Option<FileWorkerGuard>> { + let guard = self.logs.init_tracing()?; + Ok(guard) + } +}
diff --git reth/crates/scroll/cli/src/spec.rs scroll-reth/crates/scroll/cli/src/spec.rs new file mode 100644 index 0000000000000000000000000000000000000000..361029d9d1fc6101bd9f15b0d01b824a3aff04ec --- /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(parse_genesis(s)?.into()), + }) + } +}
diff --git reth/crates/scroll/consensus/Cargo.toml scroll-reth/crates/scroll/consensus/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..0411a3f347ba9b750b242f22e95be5c08d5e1d9e --- /dev/null +++ scroll-reth/crates/scroll/consensus/Cargo.toml @@ -0,0 +1,38 @@ +[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 + +# reth +reth-chainspec.workspace = true +reth-consensus.workspace = true +reth-consensus-common.workspace = true +reth-ethereum-consensus.workspace = true +reth-execution-types.workspace = true +# imported in order to pull in the reth-codec feature for reth-ethereum-primitives +reth-primitives = { workspace = true, features = ["reth-codec"] } +reth-primitives-traits.workspace = true + +# scroll +reth-scroll-primitives = { workspace = true, features = ["serde", "reth-codec"] } +scroll-alloy-hardforks.workspace = true + +# misc +thiserror.workspace = true +tracing.workspace = true + +[package.metadata.cargo-udeps.ignore] +normal = ["reth-primitives"]
diff --git reth/crates/scroll/consensus/src/error.rs scroll-reth/crates/scroll/consensus/src/error.rs new file mode 100644 index 0000000000000000000000000000000000000000..39bae3f2eaf0c536b4ea5ed1dd69f4722329a02d --- /dev/null +++ scroll-reth/crates/scroll/consensus/src/error.rs @@ -0,0 +1,15 @@ +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), + /// 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, +}
diff --git reth/crates/scroll/consensus/src/lib.rs scroll-reth/crates/scroll/consensus/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..65cf8b63dcc60e5966d8e97b4d1c7347c0acd81e --- /dev/null +++ scroll-reth/crates/scroll/consensus/src/lib.rs @@ -0,0 +1,9 @@ +//! Scroll consensus implementation. + +extern crate alloc; + +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..551efacf65eb283bee693dbf2ebbceac0c268f41 --- /dev/null +++ scroll-reth/crates/scroll/consensus/src/validation.rs @@ -0,0 +1,191 @@ +use alloc::sync::Arc; +use core::fmt::Debug; + +use crate::error::ScrollConsensusError; +use alloy_consensus::{BlockHeader as _, TxReceipt, EMPTY_OMMER_ROOT_HASH}; +use alloy_primitives::B256; +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::{ + receipt::gas_spent_by_transactions, Block, BlockBody, BlockHeader, GotExpected, NodePrimitives, + RecoveredBlock, SealedBlock, SealedHeader, +}; +use reth_scroll_primitives::ScrollReceipt; +use scroll_alloy_hardforks::{ScrollHardfork, 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>> + 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(()) + } +} + +impl<ChainSpec: EthChainSpec + ScrollHardforks, B: Block> Consensus<B> + for ScrollBeaconConsensus<ChainSpec> +{ + 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 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())) + } + + Ok(()) + } +} + +impl<ChainSpec: EthChainSpec + ScrollHardforks, H: BlockHeader> HeaderValidator<H> + for ScrollBeaconConsensus<ChainSpec> +{ + fn validate_header(&self, header: &SealedHeader<H>) -> Result<(), ConsensusError> { + if header.ommers_hash() != EMPTY_OMMER_ROOT_HASH { + return Err(ConsensusError::TheMergeOmmerRootIsNotEmpty) + } + + validate_header_gas(header.header())?; + validate_header_base_fee(header.header(), &self.chain_spec) + } + + 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())?; + + // TODO(scroll): we should have a way to validate the base fee from the header + // against the parent header using + // <https://github.com/scroll-tech/go-ethereum/blob/develop/consensus/misc/eip1559.go#L53> + + // 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(()) + } +} + +/// 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<(), ConsensusError> { + if chain_spec.scroll_fork_activation(ScrollHardfork::Curie).active_at_block(header.number()) && + header.base_fee_per_gas().is_none() + { + return Err(ConsensusError::BaseFeeMissing) + } + 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(()) +}
diff --git reth/crates/scroll/engine-primitives/Cargo.toml scroll-reth/crates/scroll/engine-primitives/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..0e11f7b1d42e3a77a2ea230385c5f2f62acf0051 --- /dev/null +++ scroll-reth/crates/scroll/engine-primitives/Cargo.toml @@ -0,0 +1,64 @@ +[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", +]
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..fa6ed8308b49fce1c1cf5af446d03e4cfd425c0f --- /dev/null +++ scroll-reth/crates/scroll/engine-primitives/src/payload/attributes.rs @@ -0,0 +1,195 @@ +//! 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 pre-Euclid block data hint, necessary for the block builder to derive the correct block + /// hash. + pub block_data_hint: Option<BlockDataHint>, +} + +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, + }) + } + + 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(block_data) = &attributes.block_data_hint { + hasher.update(&block_data.extra_data); + hasher.update(block_data.difficulty.to_be_bytes::<32>()); + } + + 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("0x0322b5f17cf26e85").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: Some(BlockDataHint{ extra_data: bytes!("476574682f76312e302e302f6c696e75782f676f312e342e32"), difficulty: U256::from(10) } ), + }; + + 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..84084cfd5597fbbf624e5cc7f349e5e22fb74e31 --- /dev/null +++ scroll-reth/crates/scroll/engine-primitives/src/payload/mod.rs @@ -0,0 +1,330 @@ +//! 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_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_chainspec::ScrollChainSpec; +use reth_scroll_primitives::ScrollBlock; +use scroll_alloy_hardforks::ScrollHardfork; +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; +} + +/// 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>( + value: ExecutionData, + chainspec: Arc<ScrollChainSpec>, +) -> 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>( + payload: ExecutionPayloadV1, + chainspec: Arc<ScrollChainSpec>, +) -> 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_fork_active_at_block(ScrollHardfork::Curie, 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: Default::default(), + 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>( + payload: ExecutionPayloadV2, + chainspec: Arc<ScrollChainSpec>, +) -> 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>( + payload: ExecutionPayloadV3, + chainspec: Arc<ScrollChainSpec>, +) -> 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..be87cc1801ee808eb95d0edf96fc5d1d25007649 --- /dev/null +++ scroll-reth/crates/scroll/evm/Cargo.toml @@ -0,0 +1,76 @@ +[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-codec"] } +reth-primitives-traits.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", "reth-codec"] } + +# alloy +alloy-consensus.workspace = true +alloy-eips.workspace = true +alloy-evm.workspace = true +alloy-primitives.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] +eyre.workspace = true +alloy-primitives = { workspace = true, features = ["getrandom"] } + +[features] +default = ["std"] +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", +]
diff --git reth/crates/scroll/evm/src/build.rs scroll-reth/crates/scroll/evm/src/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..58c8e31d00b2d980bd9fec9eac26c293955cd35a --- /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, + 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) + .then_some(evm_env.block_env.basefee), + number: evm_env.block_env.number, + 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..f20929c7c6289d4fb725f4f37ca0d707420a62df --- /dev/null +++ scroll-reth/crates/scroll/evm/src/config.rs @@ -0,0 +1,304 @@ +use crate::{build::ScrollBlockAssembler, ScrollEvmConfig}; +use alloy_consensus::{BlockHeader, Header}; +use alloy_evm::{FromRecoveredTx, FromTxWithEncoded}; +use reth_chainspec::EthChainSpec; +use reth_evm::{ConfigureEvm, EvmEnv, ExecutionCtxFor, NextBlockEnvAttributes}; +use reth_primitives_traits::{ + BlockTy, NodePrimitives, SealedBlock, SealedHeader, SignedTransaction, +}; +use reth_scroll_chainspec::{ChainConfig, ScrollChainConfig}; +use reth_scroll_primitives::ScrollReceipt; +use revm::{ + context::{BlockEnv, CfgEnv, TxEnv}, + primitives::U256, +}; +use revm_scroll::ScrollSpecId; +use scroll_alloy_evm::{ + ScrollBlockExecutionCtx, ScrollBlockExecutorFactory, ScrollReceiptBuilder, + ScrollTransactionIntoTxEnv, +}; +use scroll_alloy_hardforks::ScrollHardforks; +use std::{convert::Infallible, sync::Arc}; + +impl<ChainSpec, N, R> ConfigureEvm for ScrollEvmConfig<ChainSpec, N, R> +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>, + Self: Send + Sync + Unpin + Clone + 'static, +{ + type Primitives = N; + type Error = Infallible; + type NextBlockEnvCtx = NextBlockEnvAttributes; + type BlockExecutorFactory = ScrollBlockExecutorFactory<R, Arc<ChainSpec>>; + 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: header.number(), + beneficiary: coinbase, + timestamp: 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: parent.number() + 1, + beneficiary: coinbase, + timestamp: attributes.timestamp, + difficulty: U256::ZERO, + prevrandao: Some(attributes.prev_randao), + gas_limit: attributes.gas_limit, + // calculate basefee based on parent block's gas usage + // TODO(scroll): update with correct block fee calculation for block building. + basefee: 100, + 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() } + } +} + +#[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 bernouilli_head = &Head { number: 5220340, ..Default::default() }; + let pre_bernouilli_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(bernouilli_head.timestamp, bernouilli_head.number), + ScrollSpecId::BERNOULLI + ); + assert_eq!( + config.spec_id_at_timestamp_and_number( + pre_bernouilli_head.timestamp, + pre_bernouilli_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 bernouilli_header = Header { number: 5220340, ..Default::default() }; + + // fill cfg env + let env = config.evm_env(&bernouilli_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_bernouilli_header = Header { number: 0, ..Default::default() }; + + // fill cfg env + let env = config.evm_env(&pre_bernouilli_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: header.number, + beneficiary: config.chain_spec().config.fee_vault_address.unwrap(), + timestamp: 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 = NextBlockEnvAttributes { + timestamp: 1719994277, + suggested_fee_recipient: Address::random(), + prev_randao: B256::random(), + gas_limit: 10000000, + parent_beacon_block_root: None, + withdrawals: None, + }; + + // 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: header.number + 1, + beneficiary: config.chain_spec().config.fee_vault_address.unwrap(), + timestamp: attributes.timestamp, + prevrandao: Some(attributes.prev_randao), + difficulty: U256::ZERO, + // TODO(scroll): this shouldn't be 0 at curie fork + basefee: 100, + 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..d20b5c22b29d08cfbe7be444a093bbb8872b1ab6 --- /dev/null +++ scroll-reth/crates/scroll/evm/src/execute.rs @@ -0,0 +1,396 @@ +//! Execution primitives for EVM. + +use crate::{receipt::ScrollRethReceiptBuilder, ScrollEvmConfig}; +use std::{fmt::Debug, sync::Arc}; + +use alloy_consensus::BlockHeader; +use alloy_primitives::{Address, B256}; +use reth_evm::execute::BasicBlockExecutorProvider; +use reth_primitives::SealedBlock; +use reth_primitives_traits::Block; +use reth_scroll_chainspec::ScrollChainSpec; + +/// 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. +#[derive(Debug)] +pub struct ScrollExecutorProvider; + +impl ScrollExecutorProvider { + /// Creates a new default scroll executor provider. + pub fn scroll(chain_spec: Arc<ScrollChainSpec>) -> BasicBlockExecutorProvider<ScrollEvmConfig> { + BasicBlockExecutorProvider::new(ScrollEvmConfig::new( + chain_spec, + ScrollRethReceiptBuilder::default(), + )) + } +} + +#[cfg(test)] +mod tests { + use crate::{ScrollEvmConfig, ScrollRethReceiptBuilder}; + use std::{convert::Infallible, sync::Arc}; + + use alloy_consensus::{Block, BlockBody, Header}; + use alloy_evm::{ + block::{BlockExecutionResult, BlockExecutor}, + Evm, + }; + 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, ScrollTxType, ScrollTypedTransaction}; + use scroll_alloy_evm::{ + 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, + }, + 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 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>, + 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, chain_spec, receipt_builder) + } + + fn block( + number: 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, gas_limit: BLOCK_GAS_LIMIT, ..Default::default() }, + body: BlockBody { transactions, ..Default::default() }, + }, + senders, + ) + } + + fn transaction(typ: ScrollTxType, gas_limit: u64) -> ScrollTransactionSigned { + let transaction = match typ { + ScrollTxType::Legacy => ScrollTypedTransaction::Legacy(alloy_consensus::TxLegacy { + to: TxKind::Call(Address::ZERO), + chain_id: Some(SCROLL_CHAIN_ID), + gas_limit, + ..Default::default() + }), + ScrollTxType::Eip2930 => ScrollTypedTransaction::Eip2930(alloy_consensus::TxEip2930 { + to: TxKind::Call(Address::ZERO), + chain_id: SCROLL_CHAIN_ID, + gas_limit, + ..Default::default() + }), + ScrollTxType::Eip1559 => ScrollTypedTransaction::Eip1559(alloy_consensus::TxEip1559 { + to: TxKind::Call(Address::ZERO), + chain_id: SCROLL_CHAIN_ID, + gas_limit, + ..Default::default() + }), + ScrollTxType::L1Message => { + ScrollTypedTransaction::L1Message(scroll_alloy_consensus::TxL1Message { + sender: Address::random(), + to: Address::ZERO, + gas_limit, + ..Default::default() + }) + } + }; + + let pk = B256::random(); + let signature = reth_primitives::sign_message(pk, transaction.signature_hash()).unwrap(); + ScrollTransactionSigned::new_unhashed(transaction, signature) + } + + fn execute_transaction( + tx_type: ScrollTxType, + block_number: 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, 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_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 res = strategy + .execute_transaction(transaction.try_into_recovered().unwrap().as_recovered_ref()); + + // 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 inner = alloy_consensus::Receipt { + cumulative_gas_used: MIN_TRANSACTION_GAS, + 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::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(7096836, 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 bytecode = Bytecode::new_raw(CURIE_L1_GAS_PRICE_ORACLE_BYTECODE); + assert_eq!(oracle.info.unwrap().code.unwrap(), 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(7096837, 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, vec![transaction.clone()]); + + // init strategy + let mut state = state(); + let mut strategy = executor(&block, &mut state); + + // execute and verify error + let res = strategy.execute_transaction( + transaction.try_into_recovered().expect("failed to recover tx").as_recovered_ref(), + ); + 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, 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, 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, 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, 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, + U256::ZERO, + Some("Eip2930 is not supported"), + )?; + 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, expected_l1_fee, None)?; + Ok(()) + } + + #[test] + fn test_execute_transactions_eip_not_curie_fork() -> eyre::Result<()> { + // Execute eip1559 transaction before curie block + execute_transaction( + ScrollTxType::Eip1559, + NOT_CURIE_BLOCK_NUMBER, + U256::ZERO, + Some("Eip1559 is not supported"), + )?; + Ok(()) + } +}
diff --git reth/crates/scroll/evm/src/l1.rs scroll-reth/crates/scroll/evm/src/l1.rs new file mode 100644 index 0000000000000000000000000000000000000000..40591639f9e3886605a6d5bfa57d69b4ddf98476 --- /dev/null +++ scroll-reth/crates/scroll/evm/src/l1.rs @@ -0,0 +1,44 @@ +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], + 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], + 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)) + } +}
diff --git reth/crates/scroll/evm/src/lib.rs scroll-reth/crates/scroll/evm/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..cbc7f590f36a11f424a3a31e1cd7b0c3f02459d0 --- /dev/null +++ scroll-reth/crates/scroll/evm/src/lib.rs @@ -0,0 +1,120 @@ +//! Scroll evm execution implementation. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +mod build; + +mod config; + +pub use execute::{ScrollBlockExecutionInput, ScrollExecutorProvider}; +mod execute; + +mod l1; +pub use l1::RethL1BlockInfo; + +pub use receipt::ScrollRethReceiptBuilder; +mod receipt; + +use crate::build::ScrollBlockAssembler; +use std::sync::Arc; + +use alloy_primitives::{BlockNumber, BlockTimestamp}; +use reth_primitives_traits::NodePrimitives; +use reth_scroll_chainspec::ScrollChainSpec; +use reth_scroll_primitives::ScrollPrimitives; +use revm_scroll::ScrollSpecId; +use scroll_alloy_evm::{ScrollBlockExecutorFactory, ScrollEvmFactory}; +use scroll_alloy_hardforks::{ScrollHardfork, ScrollHardforks}; + +/// Scroll EVM configuration. +#[derive(Debug)] +pub struct ScrollEvmConfig< + ChainSpec = ScrollChainSpec, + N: NodePrimitives = ScrollPrimitives, + R = ScrollRethReceiptBuilder, +> { + /// Executor factory. + executor_factory: ScrollBlockExecutorFactory<R, Arc<ChainSpec>>, + /// 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> Clone for ScrollEvmConfig<ChainSpec, N, R> { + 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> ScrollEvmConfig<ChainSpec, N, R> { + /// 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::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 + } +}
diff --git reth/crates/scroll/evm/src/receipt.rs scroll-reth/crates/scroll/evm/src/receipt.rs new file mode 100644 index 0000000000000000000000000000000000000000..0494e993b6dfd06648ceaf6ed4d74926189db4c2 --- /dev/null +++ scroll-reth/crates/scroll/evm/src/receipt.rs @@ -0,0 +1,36 @@ +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::L1Message => ScrollReceipt::L1Message(inner), + } + } +}
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..fb7d068fedbdeaca76ad435a112f1c60ec7dded2 --- /dev/null +++ scroll-reth/crates/scroll/hardforks/docs/hardforks.md @@ -0,0 +1,343 @@ +--- +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..fd139796a272ebfe0a460f707cbe1a0daa1baff3 --- /dev/null +++ scroll-reth/crates/scroll/hardforks/src/lib.rs @@ -0,0 +1,83 @@ +//! 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)), + ]) +}); + +/// 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)), + ]) +}); + +/// 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)), + ]) +});
diff --git reth/crates/scroll/node/Cargo.toml scroll-reth/crates/scroll/node/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..b94b463aa01e2dfbef83da9e6cab74960fd31869 --- /dev/null +++ scroll-reth/crates/scroll/node/Cargo.toml @@ -0,0 +1,124 @@ +[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-basic-payload-builder.workspace = true +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-eips.workspace = true +alloy-genesis = { workspace = true, optional = true } +alloy-primitives.workspace = true +alloy-rpc-types-engine.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-rpc-types-engine.workspace = true + +# misc +eyre.workspace = true +serde_json = { workspace = true, optional = true } +tracing.workspace = true +thiserror.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-network.workspace = true +alloy-consensus.workspace = true +futures.workspace = true + +[features] +default = ["reth-codec", "scroll-alloy-traits"] +reth-codec = [ + "reth-scroll-primitives/reth-codec", +] +skip-state-root-validation = [ + "reth-provider/skip-state-root-validation", + "reth-node-builder/skip-state-root-validation", +] +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..3496b54da6cb6d5fc83ac7b64f390ba4cc71b2de --- /dev/null +++ scroll-reth/crates/scroll/node/src/addons.rs @@ -0,0 +1,142 @@ +use crate::{ScrollEngineValidator, ScrollEngineValidatorBuilder, ScrollStorage}; +use reth_evm::{ConfigureEvm, EvmFactory, EvmFactoryFor, NextBlockEnvAttributes}; +use reth_node_api::{AddOnsContext, NodeAddOns}; +use reth_node_builder::{ + rpc::{ + BasicEngineApiBuilder, EngineValidatorAddOn, EngineValidatorBuilder, EthApiBuilder, + RethRpcAddOns, 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_primitives::ScrollPrimitives; +use reth_scroll_rpc::{eth::ScrollEthApiBuilder, ScrollEthApi, ScrollEthApiError}; +use revm::context::TxEnv; +use scroll_alloy_evm::ScrollTransactionIntoTxEnv; + +/// Add-ons for the Scroll follower node. +#[derive(Debug)] +pub struct ScrollAddOns<N> +where + N: FullNodeComponents, + 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>, + >, +} + +impl<N> Default for ScrollAddOns<N> +where + N: FullNodeComponents<Types: NodeTypes<Primitives = ScrollPrimitives>>, + ScrollEthApiBuilder: EthApiBuilder<N>, +{ + fn default() -> Self { + Self::builder().build() + } +} + +impl<N> ScrollAddOns<N> +where + N: FullNodeComponents<Types: NodeTypes<Primitives = ScrollPrimitives>>, + ScrollEthApiBuilder: EthApiBuilder<N>, +{ + /// Build a [`ScrollAddOns`] using [`ScrollAddOnsBuilder`]. + pub fn builder() -> ScrollAddOnsBuilder { + ScrollAddOnsBuilder::default() + } +} + +impl<N> NodeAddOns<N> for ScrollAddOns<N> +where + N: FullNodeComponents< + Types: NodeTypes< + ChainSpec = ScrollChainSpec, + Primitives = ScrollPrimitives, + Storage = ScrollStorage, + Payload = ScrollEngineTypes, + >, + Evm: ConfigureEvm<NextBlockEnvCtx = NextBlockEnvAttributes>, + >, + ScrollEthApiError: FromEvmError<N::Evm>, + EvmFactoryFor<N::Evm>: EvmFactory<Tx = ScrollTransactionIntoTxEnv<TxEnv>>, +{ + type Handle = RpcHandle<N, ScrollEthApi<N>>; + + async fn launch_add_ons( + self, + ctx: reth_node_api::AddOnsContext<'_, N>, + ) -> eyre::Result<Self::Handle> { + let Self { rpc_add_ons } = self; + rpc_add_ons.launch_add_ons_with(ctx, |_, _, _| Ok(())).await + } +} + +impl<N> RethRpcAddOns<N> for ScrollAddOns<N> +where + N: FullNodeComponents< + Types: NodeTypes< + ChainSpec = ScrollChainSpec, + Primitives = ScrollPrimitives, + Storage = ScrollStorage, + Payload = ScrollEngineTypes, + >, + Evm: ConfigureEvm<NextBlockEnvCtx = NextBlockEnvAttributes>, + >, + ScrollEthApiError: FromEvmError<N::Evm>, + EvmFactoryFor<N::Evm>: EvmFactory<Tx = ScrollTransactionIntoTxEnv<TxEnv>>, +{ + type EthApi = ScrollEthApi<N>; + + 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, + >, + >, + ScrollEthApiBuilder: EthApiBuilder<N>, +{ + type Validator = ScrollEngineValidator; + + async fn engine_validator(&self, ctx: &AddOnsContext<'_, N>) -> eyre::Result<Self::Validator> { + ScrollEngineValidatorBuilder.build(ctx).await + } +} + +/// A regular scroll evm and executor builder. +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct ScrollAddOnsBuilder {} + +impl ScrollAddOnsBuilder { + /// Builds an instance of [`ScrollAddOns`]. + pub fn build<N>(self) -> ScrollAddOns<N> + where + N: FullNodeComponents<Types: NodeTypes<Primitives = ScrollPrimitives>>, + ScrollEthApiBuilder: EthApiBuilder<N>, + { + ScrollAddOns { + rpc_add_ons: RpcAddOns::new( + ScrollEthApi::<N>::builder(), + Default::default(), + Default::default(), + ), + } + } +}
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..fcb25235ad1dba7d64ac554e8832c5f865d6054b --- /dev/null +++ scroll-reth/crates/scroll/node/src/builder/consensus.rs @@ -0,0 +1,28 @@ +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_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>, + >, + >, +{ + 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..5ffcd8ade963e3579d7d4bd7a64ca7db6ef10d0b --- /dev/null +++ scroll-reth/crates/scroll/node/src/builder/engine.rs @@ -0,0 +1,122 @@ +use alloy_primitives::U256; +use alloy_rpc_types_engine::{ExecutionData, PayloadError}; +use reth_node_api::{NewPayloadError, PayloadValidator}; +use reth_node_builder::{ + rpc::EngineValidatorBuilder, AddOnsContext, EngineApiMessageVersion, + EngineObjectValidationError, EngineTypes, EngineValidator, FullNodeComponents, + PayloadOrAttributes, +}; +use reth_node_types::NodeTypes; +use reth_primitives_traits::{Block as _, RecoveredBlock}; +use reth_scroll_chainspec::ScrollChainSpec; +use reth_scroll_engine_primitives::{try_into_block, ScrollEngineTypes}; +use reth_scroll_primitives::{ScrollBlock, ScrollPrimitives}; +use scroll_alloy_rpc_types_engine::ScrollPayloadAttributes; +use std::sync::Arc; + +/// The block difficulty for in turn signing in the Clique consensus. +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. +const CLIQUE_NO_TURN_DIFFICULTY: U256 = U256::from_limbs([1, 0, 0, 0]); + +/// Builder for [`ScrollEngineValidator`]. +#[derive(Debug, Default, Clone, Copy)] +pub struct ScrollEngineValidatorBuilder; + +impl<Node, Types> EngineValidatorBuilder<Node> for ScrollEngineValidatorBuilder +where + Types: NodeTypes< + ChainSpec = ScrollChainSpec, + Primitives = ScrollPrimitives, + Payload = ScrollEngineTypes, + >, + Node: FullNodeComponents<Types = Types>, +{ + type Validator = ScrollEngineValidator; + + async fn build(self, ctx: &AddOnsContext<'_, Node>) -> eyre::Result<Self::Validator> { + let chainspec = ctx.config.chain.clone(); + Ok(ScrollEngineValidator { chainspec }) + } +} + +/// Scroll engine validator. +#[derive(Debug, Clone)] +pub struct ScrollEngineValidator { + chainspec: Arc<ScrollChainSpec>, +} + +impl ScrollEngineValidator { + /// Returns a new [`ScrollEngineValidator`]. + pub const fn new(chainspec: Arc<ScrollChainSpec>) -> Self { + Self { chainspec } + } +} + +impl<Types> EngineValidator<Types> for ScrollEngineValidator +where + Types: EngineTypes<PayloadAttributes = ScrollPayloadAttributes, ExecutionData = ExecutionData>, +{ + fn validate_version_specific_fields( + &self, + _version: EngineApiMessageVersion, + _payload_or_attrs: PayloadOrAttributes<'_, Self::ExecutionData, ScrollPayloadAttributes>, + ) -> Result<(), EngineObjectValidationError> { + Ok(()) + } + + fn ensure_well_formed_attributes( + &self, + _version: EngineApiMessageVersion, + _attributes: &ScrollPayloadAttributes, + ) -> Result<(), EngineObjectValidationError> { + Ok(()) + } +} + +impl PayloadValidator for ScrollEngineValidator { + type Block = ScrollBlock; + type ExecutionData = ExecutionData; + + 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 and return if hashes match + let block_hash = block.hash_slow(); + if block_hash == expected_hash { + return block + .seal_unchecked(block_hash) + .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())); + } + + // Seal the block with the no-turn difficulty and return if hashes match + 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())); + } + + Err(PayloadError::BlockHash { execution: block_hash_no_turn, consensus: expected_hash } + .into()) + } +}
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..a43cf83af14035a01943e0704e5cab95984b3984 --- /dev/null +++ scroll-reth/crates/scroll/node/src/builder/execution.rs @@ -0,0 +1,30 @@ +use reth_evm::execute::BasicBlockExecutorProvider; +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; + type Executor = BasicBlockExecutorProvider<Self::EVM>; + + async fn build_evm( + self, + ctx: &BuilderContext<Node>, + ) -> eyre::Result<(Self::EVM, Self::Executor)> { + let evm_config = ScrollEvmConfig::scroll(ctx.chain_spec()); + let executor = BasicBlockExecutorProvider::new(evm_config.clone()); + + Ok((evm_config, executor)) + } +}
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..56e16f11e468a8c130342667730b20a0eddf68a5 --- /dev/null +++ scroll-reth/crates/scroll/node/src/builder/network.rs @@ -0,0 +1,60 @@ +use reth_network::{ + config::NetworkMode, NetworkConfig, NetworkManager, NetworkPrimitives, PeersInfo, +}; +use reth_node_api::TxTy; +use reth_node_builder::{components::NetworkBuilder, BuilderContext, FullNodeTypes}; +use reth_node_types::NodeTypes; +use reth_scroll_chainspec::ScrollChainSpec; +use reth_scroll_primitives::{ + ScrollBlock, ScrollBlockBody, ScrollPrimitives, ScrollReceipt, ScrollTransactionSigned, +}; +use reth_tracing::tracing::info; +use reth_transaction_pool::{PoolTransaction, TransactionPool}; + +/// The network builder for Scroll. +#[derive(Debug, Default, Clone, Copy)] +pub struct ScrollNetworkBuilder; + +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 Primitives = ScrollNetworkPrimitives; + + async fn build_network( + self, + ctx: &BuilderContext<Node>, + pool: Pool, + ) -> eyre::Result<reth_network::NetworkHandle<Self::Primitives>> { + // set the network mode to work. + let config = ctx.network_config()?; + let config = NetworkConfig { network_mode: NetworkMode::Work, ..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. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub struct ScrollNetworkPrimitives; + +impl NetworkPrimitives for ScrollNetworkPrimitives { + type BlockHeader = alloy_consensus::Header; + type BlockBody = ScrollBlockBody; + type Block = ScrollBlock; + type BroadcastedTransaction = ScrollTransactionSigned; + type PooledTransaction = scroll_alloy_consensus::ScrollPooledTransaction; + type Receipt = ScrollReceipt; +}
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..c88c43f477c4cb51d07dbd6da18217259c515fba --- /dev/null +++ scroll-reth/crates/scroll/node/src/builder/payload.rs @@ -0,0 +1,80 @@ +use reth_evm::{ConfigureEvm, NextBlockEnvAttributes}; +use reth_node_api::PrimitivesTy; +use reth_node_builder::{components::PayloadBuilderBuilder, BuilderContext, FullNodeTypes}; +use reth_node_types::{NodeTypes, TxTy}; +use reth_scroll_chainspec::ScrollChainSpec; +use reth_scroll_engine_primitives::ScrollEngineTypes; +use reth_scroll_payload::ScrollPayloadTransactions; +use reth_scroll_primitives::{ScrollPrimitives, ScrollTransactionSigned}; +use reth_transaction_pool::{PoolTransaction, TransactionPool}; + +/// Payload builder for Scroll. +#[derive(Debug, Clone, Default, Copy)] +pub struct ScrollPayloadBuilder<Txs = ()> { + /// Returns the current best transactions from the mempool. + pub best_transactions: Txs, +} + +impl<Txs> ScrollPayloadBuilder<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 payload_builder = reth_scroll_payload::ScrollPayloadBuilder::new( + pool, + evm_config, + ctx.provider().clone(), + ) + .with_transactions(self.best_transactions); + + Ok(payload_builder) + } +} + +impl<Node, Pool, Txs, Evm> PayloadBuilderBuilder<Node, Pool, Evm> for ScrollPayloadBuilder<Txs> +where + Node: FullNodeTypes< + Types: NodeTypes< + Payload = ScrollEngineTypes, + ChainSpec = ScrollChainSpec, + Primitives = ScrollPrimitives, + >, + >, + Evm: ConfigureEvm< + Primitives = PrimitivesTy<Node::Types>, + NextBlockEnvCtx = NextBlockEnvAttributes, + > + '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..81e2f9215035f995db1dbfb2af98fb3f98c44869 --- /dev/null +++ scroll-reth/crates/scroll/node/src/builder/pool.rs @@ -0,0 +1,120 @@ +use reth_node_api::{FullNodeTypes, NodeTypes}; +use reth_node_builder::{ + components::{PoolBuilder, PoolBuilderConfigOverrides}, + BuilderContext, TxTy, +}; + +use reth_provider::CanonStateSubscriptions; +use reth_scroll_txpool::{ScrollTransactionPool, ScrollTransactionValidator}; +use reth_transaction_pool::{ + blobstore::DiskFileBlobStore, CoinbaseTipOrdering, EthPoolTransaction, + TransactionValidationTaskExecutor, +}; +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: ScrollHardforks>>, + T: EthPoolTransaction<Consensus = TxTy<Node::Types>>, +{ + 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_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); + + 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, + 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) + } +}
diff --git reth/crates/scroll/node/src/lib.rs scroll-reth/crates/scroll/node/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..6ce01c39395492fd1ac5e7bea9a87b9f971a4b9f --- /dev/null +++ scroll-reth/crates/scroll/node/src/lib.rs @@ -0,0 +1,25 @@ +//! Node specific implementations for Scroll. +mod builder; +pub use builder::{ + consensus::ScrollConsensusBuilder, + engine::{ScrollEngineValidator, ScrollEngineValidatorBuilder}, + execution::ScrollExecutorBuilder, + network::{ScrollNetworkBuilder, ScrollNetworkPrimitives}, + payload::ScrollPayloadBuilder, + pool::ScrollPoolBuilder, +}; + +mod addons; +pub use addons::{ScrollAddOns, ScrollAddOnsBuilder}; + +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..36cc46ef58c343ca45e124781530066457376a5b --- /dev/null +++ scroll-reth/crates/scroll/node/src/node.rs @@ -0,0 +1,83 @@ +//! Node specific implementations for Scroll. + +use crate::{ + ScrollAddOns, ScrollConsensusBuilder, ScrollExecutorBuilder, ScrollNetworkBuilder, + ScrollPayloadBuilder, ScrollPoolBuilder, ScrollStorage, +}; +use reth_node_builder::{ + components::{BasicPayloadServiceBuilder, ComponentsBuilder}, + node::{FullNodeTypes, NodeTypes}, + Node, NodeAdapter, NodeComponentsBuilder, +}; +use reth_scroll_chainspec::ScrollChainSpec; +use reth_scroll_engine_primitives::ScrollEngineTypes; +use reth_scroll_primitives::ScrollPrimitives; +use reth_trie_db::MerklePatriciaTrie; + +/// The Scroll node implementation. +#[derive(Clone, Debug, Default)] +pub struct ScrollNode; + +impl ScrollNode { + /// Returns a [`ComponentsBuilder`] configured for a regular Ethereum node. + pub fn components<Node>() -> ComponentsBuilder< + Node, + ScrollPoolBuilder, + BasicPayloadServiceBuilder<ScrollPayloadBuilder>, + 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(ScrollPayloadBuilder::default())) + .network(ScrollNetworkBuilder) + .executor(ScrollExecutorBuilder) + .consensus(ScrollConsensusBuilder) + } +} + +impl<N> Node<N> for ScrollNode +where + N: FullNodeTypes<Types = Self>, +{ + type ComponentsBuilder = ComponentsBuilder< + N, + ScrollPoolBuilder, + BasicPayloadServiceBuilder<ScrollPayloadBuilder>, + 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 { + ScrollAddOns::default() + } +} + +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..1394f9e99c8ac5ba37e8486adbc527fa1d33e5e7 --- /dev/null +++ scroll-reth/crates/scroll/node/src/test_utils.rs @@ -0,0 +1,82 @@ +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::ScrollChainSpecBuilder; +use reth_tasks::TaskManager; +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) + .darwin_v2_activated() + .build(Default::default()), + ), + is_dev, + 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: Some(vec![]), + parent_beacon_block_root: Some(B256::ZERO), + }; + + ScrollPayloadBuilderAttributes { + payload_attributes: EthPayloadBuilderAttributes::new(B256::ZERO, attributes), + transactions: vec![], + no_tx_pool: false, + block_data_hint: 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/payload/Cargo.toml scroll-reth/crates/scroll/payload/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..c59b4e6b96ba63a7291507eb67ec3b378c0b0533 --- /dev/null +++ scroll-reth/crates/scroll/payload/Cargo.toml @@ -0,0 +1,83 @@ +[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 +scroll-alloy-hardforks.workspace = true +scroll-alloy-consensus.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-scroll-chainspec.workspace = true +reth-scroll-forks.workspace = true +reth-storage-api.workspace = true +reth-transaction-pool.workspace = true +reth-payload-util.workspace = true + +# scroll +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] +std = [ + "futures-util/std", + "reth-payload-primitives/std", + "reth-primitives-traits/std", + "reth-scroll-engine-primitives/std", + "alloy-consensus/std", + "alloy-primitives/std", + "alloy-rlp/std", + "reth-chainspec/std", + "reth-evm/std", + "reth-execution-types/std", + "reth-revm/std", + "reth-storage-api/std", + "revm/std", + "thiserror/std", + "tracing/std", + "reth-scroll-chainspec/std", + "reth-scroll-forks/std", + "scroll-alloy-consensus/std", + "scroll-alloy-hardforks/std", +] +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..84afd7e9c68e2a078eb319aa772630b3c3c32130 --- /dev/null +++ scroll-reth/crates/scroll/payload/src/builder.rs @@ -0,0 +1,536 @@ +//! Scroll's payload builder implementation. + +use super::ScrollPayloadBuilderError; +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}; +use reth_chainspec::{ChainSpecProvider, EthChainSpec}; +use reth_evm::{ + block::{BlockExecutionError, BlockValidationError}, + execute::{BlockBuilder, BlockBuilderOutcome, ProviderError}, + ConfigureEvm, Database, Evm, NextBlockEnvAttributes, +}; +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_engine_primitives::{ScrollBuiltPayload, ScrollPayloadBuilderAttributes}; +use reth_scroll_primitives::{ScrollPrimitives, ScrollTransactionSigned}; +use reth_storage_api::{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}; + +const SCROLL_GAS_LIMIT_10M: u64 = 10_000_000; + +/// 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(Debug, Clone)] +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, +} + +impl<Pool, Evm, Client> ScrollPayloadBuilder<Pool, Client, Evm> { + /// Creates a new [`ScrollPayloadBuilder`]. + pub const fn new(pool: Pool, evm_config: Evm, client: Client) -> Self { + Self { evm_config, pool, client, best_transactions: () } + } +} + +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, .. } = self; + ScrollPayloadBuilder { evm_config, pool, client, best_transactions } + } +} + +impl<Pool, Client, Evm, T> ScrollPayloadBuilder<Pool, Client, Evm, T> +where + Pool: TransactionPool<Transaction: PoolTransaction<Consensus = ScrollTransactionSigned>>, + Client: StateProviderFactory + ChainSpecProvider<ChainSpec: EthChainSpec + ScrollHardforks>, + Evm: ConfigureEvm<Primitives = ScrollPrimitives, NextBlockEnvCtx = NextBlockEnvAttributes>, +{ + /// Constructs an 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 an 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) + } else { + // sequencer mode we can reuse cachedreads from previous runs + builder.build(cached_reads.as_db_mut(state), &state_provider, ctx) + } + .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> + Clone, + Pool: TransactionPool<Transaction: PoolTransaction<Consensus = ScrollTransactionSigned>>, + Evm: ConfigureEvm<Primitives = ScrollPrimitives, NextBlockEnvCtx = NextBlockEnvAttributes>, + 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>, + ) -> Result<BuildOutcomeKind<ScrollBuiltPayload>, PayloadBuilderError> + where + EvmConfig: + ConfigureEvm<Primitives = ScrollPrimitives, NextBlockEnvCtx = NextBlockEnvAttributes>, + ChainSpec: EthChainSpec + ScrollHardforks, + 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 mut db = State::builder().with_database(db).with_bundle_update().build(); + + let mut builder = ctx.block_builder(&mut db)?; + + // 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)?.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 extra data and difficulty fields using the payload attributes. + if let Some(block_data) = &ctx.config.attributes.block_data_hint { + let (mut scroll_block, senders) = block.split(); + scroll_block = scroll_block.map_header(|mut header| { + header.extra_data = block_data.extra_data.clone(); + header.difficulty = block_data.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: 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 = NextBlockEnvAttributes>, + ChainSpec: EthChainSpec + ScrollHardforks, +{ + /// 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>, + ) -> Result<impl BlockBuilder<Primitives = Evm::Primitives> + 'a, PayloadBuilderError> { + self.evm_config + .builder_for_next_block( + db, + self.parent(), + NextBlockEnvAttributes { + timestamp: self.attributes().timestamp(), + suggested_fee_recipient: self.attributes().suggested_fee_recipient(), + prev_randao: self.attributes().prev_randao(), + gas_limit: SCROLL_GAS_LIMIT_10M, + parent_beacon_block_root: self.attributes().parent_beacon_block_root(), + withdrawals: None, + }, + ) + .map_err(PayloadBuilderError::other) + } +} + +impl<Evm, ChainSpec> ScrollPayloadBuilderCtx<Evm, ChainSpec> +where + Evm: ConfigureEvm<Primitives = ScrollPrimitives, NextBlockEnvCtx = NextBlockEnvAttributes>, + ChainSpec: EthChainSpec + ScrollHardforks, +{ + /// 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>>, + >, + ) -> 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) { + // 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(())) + } + + 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, + ) -> bool { + self.cumulative_gas_used + tx.gas_limit() > block_gas_limit + } +}
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..c396b52f63e9256e806fd2dc1004a6e8f7013359 --- /dev/null +++ scroll-reth/crates/scroll/payload/src/lib.rs @@ -0,0 +1,17 @@ +//! Engine Payload related types. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +extern crate alloc as std; + +pub mod builder; +pub use builder::{ScrollPayloadBuilder, ScrollPayloadTransactions}; + +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..afb54b933d4e8e216f7bfdf0ef6e60af588a06d9 --- /dev/null +++ scroll-reth/crates/scroll/primitives/Cargo.toml @@ -0,0 +1,126 @@ +[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-evm.workspace = true +alloy-primitives.workspace = true +alloy-rlp.workspace = true + +# revm +revm-context.workspace = true + +# scroll +scroll-alloy-consensus.workspace = true +scroll-alloy-evm.workspace = true +revm-scroll.workspace = true + +# codec +bytes = { workspace = true, optional = true } +modular-bitfield = { workspace = true, optional = true } +serde = { workspace = true, optional = true } + +# misc +derive_more.workspace = true +once_cell.workspace = true +rand_08 = { workspace = true, optional = true } + +# test +arbitrary = { workspace = true, features = ["derive"], optional = true } +proptest = { workspace = true, optional = true } +secp256k1 = { workspace = true, optional = true } + +[dev-dependencies] +proptest-arbitrary-interop.workspace = true +reth-codecs = { workspace = true, features = ["test-utils"] } +rstest.workspace = true +proptest.workspace = true +secp256k1 = { workspace = true, features = ["rand"] } +rand.workspace = true +rand_08.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", + "derive_more/std", + "once_cell/std", + "proptest?/std", + "serde?/std", + "rand_08?/std", + "secp256k1?/std", + "revm-scroll/std", + "revm-context/std", + "alloy-evm/std", + "scroll-alloy-evm/std", +] +reth-codec = [ + "dep:reth-codecs", + "std", + "dep:proptest", + "dep:arbitrary", + "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", + "secp256k1?/serde", + "alloy-consensus/serde", + "alloy-eips/serde", + "alloy-primitives/serde", + "bytes?/serde", + "rand_08?/serde", + "reth-codecs?/serde", + "reth-primitives-traits/serde", + "revm-scroll/serde", + "revm-context/serde", + "scroll-alloy-evm/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", + "dep:secp256k1", + "secp256k1?/rand", + "rand_08", + "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..2f4f6cafb96c460e536a2065a2c0ef42499b2e54 --- /dev/null +++ scroll-reth/crates/scroll/primitives/src/lib.rs @@ -0,0 +1,41 @@ +//! 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 _; + +extern crate alloc; + +pub mod transaction; +pub use transaction::{signed::ScrollTransactionSigned, tx_type::ScrollTxType}; + +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..889f24bc366e0ad5fcfbe2bac80ab520e85aa2fd --- /dev/null +++ scroll-reth/crates/scroll/primitives/src/receipt.rs @@ -0,0 +1,303 @@ +use alloy_consensus::{ + proofs::ordered_trie_root_with_encoder, Eip2718EncodableReceipt, Eip658Value, Receipt, + ReceiptWithBloom, RlpDecodableReceipt, RlpEncodableReceipt, TxReceipt, Typed2718, +}; +use alloy_eips::eip2718::Encodable2718; +use alloy_primitives::{Bloom, Log, B256, U256}; +use alloy_rlp::{BufMut, Decodable, 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), + /// 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::L1Message(_) => ScrollTxType::L1Message, + } + } + + /// Returns inner [`Receipt`], + pub const fn as_receipt(&self) -> &Receipt { + match self { + Self::Legacy(receipt) | Self::Eip2930(receipt) | Self::Eip1559(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) => { + 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) => { + 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) } + } + + /// 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::L1Message => { + let ReceiptWithBloom { receipt, logs_bloom } = + RlpDecodableReceipt::rlp_decode_with_bloom(buf)?; + Ok(ReceiptWithBloom { receipt: Self::L1Message(receipt), logs_bloom }) + } + } + } + + /// 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) => { + 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 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() + } +} + +impl reth_primitives_traits::Receipt for ScrollReceipt {} + +#[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 alloc::borrow::Cow; + use alloy_primitives::U256; + use reth_codecs::Compact; + + #[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::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..0b23824d496eeb292fc66ba4ca1678f633e9c788 --- /dev/null +++ scroll-reth/crates/scroll/primitives/src/transaction/mod.rs @@ -0,0 +1,4 @@ +//! Scroll primitives transaction types. + +pub mod signed; +pub mod tx_type;
diff --git reth/crates/scroll/primitives/src/transaction/signed.rs scroll-reth/crates/scroll/primitives/src/transaction/signed.rs new file mode 100644 index 0000000000000000000000000000000000000000..27088beddb47faea39a71454194c882764226f4b --- /dev/null +++ scroll-reth/crates/scroll/primitives/src/transaction/signed.rs @@ -0,0 +1,748 @@ +//! A signed Scroll transaction. + +use crate::ScrollTxType; +use alloc::{vec, vec::Vec}; +use core::{ + hash::{Hash, Hasher}, + mem, + ops::Deref, +}; +#[cfg(feature = "std")] +use std::sync::OnceLock; + +use alloy_consensus::{ + transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx}, + SignableTransaction, Signed, Transaction, TxEip1559, TxEip2930, TxLegacy, Typed2718, +}; +use alloy_eips::{ + eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718}, + eip2930::AccessList, + eip7702::SignedAuthorization, +}; +use alloy_evm::{FromRecoveredTx, FromTxWithEncoded}; +use alloy_primitives::{keccak256, Address, Bytes, Signature, TxHash, TxKind, Uint, B256}; +use alloy_rlp::Header; +#[cfg(feature = "reth-codec")] +use arbitrary as _; +use derive_more::{AsRef, Deref}; +#[cfg(not(feature = "std"))] +use once_cell::sync::OnceCell as OnceLock; +#[cfg(any(test, feature = "reth-codec"))] +use proptest as _; +use reth_primitives_traits::{ + crypto::secp256k1::{recover_signer, recover_signer_unchecked}, + transaction::{error::TryFromRecoveredTransactionError, signed::RecoveryError}, + InMemorySize, SignedTransaction, +}; +use revm_context::TxEnv; +use scroll_alloy_consensus::{ + ScrollPooledTransaction, ScrollTypedTransaction, TxL1Message, L1_MESSAGE_TRANSACTION_TYPE, +}; +use scroll_alloy_evm::ScrollTransactionIntoTxEnv; + +/// Signed transaction. +#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(rlp))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Eq, AsRef, Deref)] +pub struct ScrollTransactionSigned { + /// Transaction hash + #[cfg_attr(feature = "serde", serde(skip))] + pub hash: OnceLock<TxHash>, + /// The transaction signature values + pub signature: Signature, + /// Raw transaction info + #[deref] + #[as_ref] + pub transaction: ScrollTypedTransaction, +} + +impl ScrollTransactionSigned { + /// Calculates hash of given transaction and signature and returns new instance. + pub fn new(transaction: ScrollTypedTransaction, signature: Signature) -> Self { + let signed_tx = Self::new_unhashed(transaction, signature); + if signed_tx.ty() != ScrollTxType::L1Message { + signed_tx.hash.get_or_init(|| signed_tx.recalculate_hash()); + } + + signed_tx + } + + /// Creates a new signed transaction from the given transaction and signature without the hash. + /// + /// Note: this only calculates the hash on the first [`ScrollTransactionSigned::hash`] call. + pub fn new_unhashed(transaction: ScrollTypedTransaction, signature: Signature) -> Self { + Self { hash: Default::default(), signature, transaction } + } + + /// Returns whether this transaction is a l1 message. + pub const fn is_l1_message(&self) -> bool { + matches!(self.transaction, ScrollTypedTransaction::L1Message(_)) + } +} + +impl SignedTransaction for ScrollTransactionSigned { + fn tx_hash(&self) -> &TxHash { + self.hash.get_or_init(|| self.recalculate_hash()) + } + + fn recover_signer(&self) -> Result<Address, RecoveryError> { + // Scroll's L1 message does not have a signature. Directly return the `sender` address. + if let ScrollTypedTransaction::L1Message(TxL1Message { sender, .. }) = self.transaction { + return Ok(sender); + } + + let Self { transaction, signature, .. } = self; + let signature_hash = transaction.signature_hash(); + recover_signer(signature, signature_hash) + } + + fn recover_signer_unchecked(&self) -> Result<Address, RecoveryError> { + // Scroll's L1 message does not have a signature. Directly return the `sender` address. + if let ScrollTypedTransaction::L1Message(TxL1Message { sender, .. }) = &self.transaction { + return Ok(*sender); + } + + let Self { transaction, signature, .. } = self; + let signature_hash = transaction.signature_hash(); + recover_signer_unchecked(signature, signature_hash) + } + + fn recover_signer_unchecked_with_buf( + &self, + buf: &mut Vec<u8>, + ) -> Result<Address, RecoveryError> { + match &self.transaction { + // Scroll's L1 message does not have a signature. Directly return the `sender` address. + ScrollTypedTransaction::L1Message(tx) => return Ok(tx.sender), + ScrollTypedTransaction::Legacy(tx) => tx.encode_for_signing(buf), + ScrollTypedTransaction::Eip2930(tx) => tx.encode_for_signing(buf), + ScrollTypedTransaction::Eip1559(tx) => tx.encode_for_signing(buf), + }; + recover_signer_unchecked(&self.signature, keccak256(buf)) + } + + fn recalculate_hash(&self) -> B256 { + keccak256(self.encoded_2718()) + } +} + +impl InMemorySize for ScrollTransactionSigned { + #[inline] + fn size(&self) -> usize { + mem::size_of::<TxHash>() + self.transaction.size() + mem::size_of::<Signature>() + } +} + +impl alloy_rlp::Encodable for ScrollTransactionSigned { + fn encode(&self, out: &mut dyn alloy_rlp::bytes::BufMut) { + self.network_encode(out); + } + + fn length(&self) -> usize { + let mut payload_length = self.encode_2718_len(); + if !self.is_legacy() { + payload_length += Header { list: false, payload_length }.length(); + } + + payload_length + } +} + +impl alloy_rlp::Decodable for ScrollTransactionSigned { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> { + Self::network_decode(buf).map_err(Into::into) + } +} + +impl Encodable2718 for ScrollTransactionSigned { + fn type_flag(&self) -> Option<u8> { + if Typed2718::is_legacy(self) { + None + } else { + Some(self.ty()) + } + } + + fn encode_2718_len(&self) -> usize { + match &self.transaction { + ScrollTypedTransaction::Legacy(legacy_tx) => { + legacy_tx.eip2718_encoded_length(&self.signature) + } + ScrollTypedTransaction::Eip2930(access_list_tx) => { + access_list_tx.eip2718_encoded_length(&self.signature) + } + ScrollTypedTransaction::Eip1559(dynamic_fee_tx) => { + dynamic_fee_tx.eip2718_encoded_length(&self.signature) + } + ScrollTypedTransaction::L1Message(l1_message) => l1_message.eip2718_encoded_length(), + } + } + + fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) { + let Self { transaction, signature, .. } = self; + + match &transaction { + ScrollTypedTransaction::Legacy(legacy_tx) => { + // do nothing w/ with_header + legacy_tx.eip2718_encode(signature, out) + } + ScrollTypedTransaction::Eip2930(access_list_tx) => { + access_list_tx.eip2718_encode(signature, out) + } + ScrollTypedTransaction::Eip1559(dynamic_fee_tx) => { + dynamic_fee_tx.eip2718_encode(signature, out) + } + ScrollTypedTransaction::L1Message(l1_message) => l1_message.encode_2718(out), + } + } +} + +impl Decodable2718 for ScrollTransactionSigned { + fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> { + match ty.try_into().map_err(|_| Eip2718Error::UnexpectedType(ty))? { + ScrollTxType::Legacy => Err(Eip2718Error::UnexpectedType(0)), + ScrollTxType::Eip2930 => { + let (tx, signature, hash) = TxEip2930::rlp_decode_signed(buf)?.into_parts(); + let signed_tx = Self::new_unhashed(ScrollTypedTransaction::Eip2930(tx), signature); + signed_tx.hash.get_or_init(|| hash); + Ok(signed_tx) + } + ScrollTxType::Eip1559 => { + let (tx, signature, hash) = TxEip1559::rlp_decode_signed(buf)?.into_parts(); + let signed_tx = Self::new_unhashed(ScrollTypedTransaction::Eip1559(tx), signature); + signed_tx.hash.get_or_init(|| hash); + Ok(signed_tx) + } + ScrollTxType::L1Message => Ok(Self::new_unhashed( + ScrollTypedTransaction::L1Message(TxL1Message::rlp_decode(buf)?), + TxL1Message::signature(), + )), + } + } + + fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> { + let (transaction, signature) = TxLegacy::rlp_decode_with_signature(buf)?; + let signed_tx = Self::new_unhashed(ScrollTypedTransaction::Legacy(transaction), signature); + + Ok(signed_tx) + } +} + +impl Transaction for ScrollTransactionSigned { + fn chain_id(&self) -> Option<u64> { + self.deref().chain_id() + } + + fn nonce(&self) -> u64 { + self.deref().nonce() + } + + fn gas_limit(&self) -> u64 { + self.deref().gas_limit() + } + + fn gas_price(&self) -> Option<u128> { + self.deref().gas_price() + } + + fn max_fee_per_gas(&self) -> u128 { + self.deref().max_fee_per_gas() + } + + fn max_priority_fee_per_gas(&self) -> Option<u128> { + self.deref().max_priority_fee_per_gas() + } + + fn max_fee_per_blob_gas(&self) -> Option<u128> { + self.deref().max_fee_per_blob_gas() + } + + fn priority_fee_or_price(&self) -> u128 { + self.deref().priority_fee_or_price() + } + + fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 { + self.deref().effective_gas_price(base_fee) + } + + fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> { + self.deref().effective_tip_per_gas(base_fee) + } + + fn is_dynamic_fee(&self) -> bool { + self.deref().is_dynamic_fee() + } + + fn kind(&self) -> TxKind { + self.deref().kind() + } + + fn is_create(&self) -> bool { + self.deref().is_create() + } + + fn value(&self) -> Uint<256, 4> { + self.deref().value() + } + + fn input(&self) -> &Bytes { + self.deref().input() + } + + fn access_list(&self) -> Option<&AccessList> { + self.deref().access_list() + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + self.deref().blob_versioned_hashes() + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + self.deref().authorization_list() + } +} + +/// A trait that allows to verify if a transaction is a l1 message. +pub trait IsL1Message { + /// Whether the transaction is a l1 transaction. + fn is_l1_message(&self) -> bool; +} + +impl IsL1Message for ScrollTransactionSigned { + fn is_l1_message(&self) -> bool { + matches!(self.transaction, ScrollTypedTransaction::L1Message(_)) + } +} + +impl Typed2718 for ScrollTransactionSigned { + fn ty(&self) -> u8 { + self.deref().ty() + } +} + +impl PartialEq for ScrollTransactionSigned { + fn eq(&self, other: &Self) -> bool { + self.signature == other.signature && + self.transaction == other.transaction && + self.tx_hash() == other.tx_hash() + } +} + +impl Hash for ScrollTransactionSigned { + fn hash<H: Hasher>(&self, state: &mut H) { + self.signature.hash(state); + self.transaction.hash(state); + } +} + +impl FromTxWithEncoded<ScrollTransactionSigned> for ScrollTransactionIntoTxEnv<TxEnv> { + fn from_encoded_tx(tx: &ScrollTransactionSigned, caller: Address, encoded: Bytes) -> Self { + let base = match &tx.transaction { + ScrollTypedTransaction::Legacy(tx) => TxEnv::from_recovered_tx(tx, caller), + ScrollTypedTransaction::Eip1559(tx) => TxEnv::from_recovered_tx(tx, caller), + ScrollTypedTransaction::Eip2930(tx) => TxEnv::from_recovered_tx(tx, caller), + ScrollTypedTransaction::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); + Self::new(base, encoded) + } +} + +#[cfg(feature = "reth-codec")] +impl reth_codecs::Compact for ScrollTransactionSigned { + fn to_compact<B>(&self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + let start = buf.as_mut().len(); + + // Placeholder for bitflags. + // The first byte uses 4 bits as flags: IsCompressed[1bit], TxType[2bits], Signature[1bit] + buf.put_u8(0); + + let sig_bit = self.signature.to_compact(buf) as u8; + let zstd_bit = self.transaction.input().len() >= 32; + + let tx_bits = if zstd_bit { + let mut tmp = Vec::with_capacity(256); + if cfg!(feature = "std") { + reth_zstd_compressors::TRANSACTION_COMPRESSOR.with(|compressor| { + let mut compressor = compressor.borrow_mut(); + let tx_bits = self.transaction.to_compact(&mut tmp); + buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress")); + tx_bits as u8 + }) + } else { + let mut compressor = reth_zstd_compressors::create_tx_compressor(); + let tx_bits = self.transaction.to_compact(&mut tmp); + buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress")); + tx_bits as u8 + } + } else { + self.transaction.to_compact(buf) as u8 + }; + + // Replace bitflags with the actual values + buf.as_mut()[start] = sig_bit | (tx_bits << 1) | ((zstd_bit as u8) << 3); + + buf.as_mut().len() - start + } + + fn from_compact(mut buf: &[u8], _len: usize) -> (Self, &[u8]) { + use bytes::Buf; + + // The first byte uses 4 bits as flags: IsCompressed[1], TxType[2], Signature[1] + let bitflags = buf.get_u8() as usize; + + let sig_bit = bitflags & 1; + let (signature, buf) = Signature::from_compact(buf, sig_bit); + + let zstd_bit = bitflags >> 3; + let (transaction, buf) = if zstd_bit != 0 { + if cfg!(feature = "std") { + reth_zstd_compressors::TRANSACTION_DECOMPRESSOR.with(|decompressor| { + let mut decompressor = decompressor.borrow_mut(); + + // TODO: enforce that zstd is only present at a "top" level type + let transaction_type = (bitflags & 0b110) >> 1; + let (transaction, _) = ScrollTypedTransaction::from_compact( + decompressor.decompress(buf), + transaction_type, + ); + + (transaction, buf) + }) + } else { + let mut decompressor = reth_zstd_compressors::create_tx_decompressor(); + let transaction_type = (bitflags & 0b110) >> 1; + let (transaction, _) = ScrollTypedTransaction::from_compact( + decompressor.decompress(buf), + transaction_type, + ); + + (transaction, buf) + } + } else { + let transaction_type = bitflags >> 1; + ScrollTypedTransaction::from_compact(buf, transaction_type) + }; + + (Self { signature, transaction, hash: Default::default() }, buf) + } +} + +#[cfg(any(test, feature = "arbitrary"))] +impl<'a> arbitrary::Arbitrary<'a> for ScrollTransactionSigned { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> { + #[allow(unused_mut)] + let mut transaction = ScrollTypedTransaction::arbitrary(u)?; + + let secp = secp256k1::Secp256k1::new(); + let key_pair = secp256k1::Keypair::new(&secp, &mut rand_08::thread_rng()); + let signature = reth_primitives_traits::crypto::secp256k1::sign_message( + B256::from_slice(&key_pair.secret_bytes()[..]), + transaction.signature_hash(), + ) + .unwrap(); + + let signature = + if is_l1_message(&transaction) { TxL1Message::signature() } else { signature }; + + Ok(Self::new(transaction, signature)) + } +} + +/// Returns `true` if transaction is l1 message. +pub const fn is_l1_message(tx: &ScrollTypedTransaction) -> bool { + matches!(tx, ScrollTypedTransaction::L1Message(_)) +} + +impl<T: Into<ScrollTypedTransaction> + RlpEcdsaEncodableTx> From<Signed<T>> + for ScrollTransactionSigned +{ + fn from(value: Signed<T>) -> Self { + let (tx, sig, hash) = value.into_parts(); + let this = Self::new(tx.into(), sig); + this.hash.get_or_init(|| hash); + this + } +} + +impl TryFrom<ScrollTransactionSigned> for ScrollPooledTransaction { + type Error = TryFromRecoveredTransactionError; + + fn try_from(value: ScrollTransactionSigned) -> Result<Self, Self::Error> { + let hash = *value.tx_hash(); + let ScrollTransactionSigned { hash: _, signature, transaction } = value; + + match transaction { + ScrollTypedTransaction::Legacy(tx) => { + Ok(Self::Legacy(Signed::new_unchecked(tx, signature, hash))) + } + ScrollTypedTransaction::Eip2930(tx) => { + Ok(Self::Eip2930(Signed::new_unchecked(tx, signature, hash))) + } + ScrollTypedTransaction::Eip1559(tx) => { + Ok(Self::Eip1559(Signed::new_unchecked(tx, signature, hash))) + } + ScrollTypedTransaction::L1Message(_) => { + Err(TryFromRecoveredTransactionError::UnsupportedTransactionType(0xfe)) + } + } + } +} + +impl From<ScrollPooledTransaction> for ScrollTransactionSigned { + fn from(value: ScrollPooledTransaction) -> Self { + match value { + ScrollPooledTransaction::Legacy(tx) => tx.into(), + ScrollPooledTransaction::Eip2930(tx) => tx.into(), + ScrollPooledTransaction::Eip1559(tx) => tx.into(), + } + } +} + +impl FromRecoveredTx<ScrollTransactionSigned> for revm_scroll::ScrollTransaction<TxEnv> { + fn from_recovered_tx(tx: &ScrollTransactionSigned, sender: Address) -> Self { + let envelope = tx.encoded_2718(); + + let base = match &tx.transaction { + ScrollTypedTransaction::Legacy(tx) => TxEnv { + gas_limit: tx.gas_limit, + gas_price: tx.gas_price, + gas_priority_fee: None, + kind: tx.to, + value: tx.value, + data: tx.input.clone(), + chain_id: tx.chain_id, + nonce: 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, + }, + ScrollTypedTransaction::Eip2930(tx) => TxEnv { + gas_limit: tx.gas_limit, + gas_price: tx.gas_price, + gas_priority_fee: None, + kind: tx.to, + value: tx.value, + data: tx.input.clone(), + chain_id: Some(tx.chain_id), + nonce: tx.nonce, + access_list: tx.access_list.clone(), + blob_hashes: Default::default(), + max_fee_per_blob_gas: Default::default(), + authorization_list: Default::default(), + tx_type: 1, + caller: sender, + }, + ScrollTypedTransaction::Eip1559(tx) => TxEnv { + gas_limit: tx.gas_limit, + gas_price: tx.max_fee_per_gas, + gas_priority_fee: Some(tx.max_priority_fee_per_gas), + kind: tx.to, + value: tx.value, + data: tx.input.clone(), + chain_id: Some(tx.chain_id), + nonce: tx.nonce, + access_list: tx.access_list.clone(), + blob_hashes: Default::default(), + max_fee_per_blob_gas: Default::default(), + authorization_list: Default::default(), + tx_type: 2, + caller: sender, + }, + ScrollTypedTransaction::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: 0, + authorization_list: vec![], + tx_type: L1_MESSAGE_TRANSACTION_TYPE, + caller: sender, + }, + }; + + Self { base, rlp_bytes: (!tx.is_l1_message()).then_some(envelope.into()) } + } +} + +impl FromRecoveredTx<ScrollTransactionSigned> for ScrollTransactionIntoTxEnv<TxEnv> { + fn from_recovered_tx(tx: &ScrollTransactionSigned, sender: Address) -> Self { + let envelope = tx.encoded_2718(); + + let base = match &tx.transaction { + ScrollTypedTransaction::Legacy(tx) => TxEnv { + gas_limit: tx.gas_limit, + gas_price: tx.gas_price, + gas_priority_fee: None, + kind: tx.to, + value: tx.value, + data: tx.input.clone(), + chain_id: tx.chain_id, + nonce: 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, + }, + ScrollTypedTransaction::Eip2930(tx) => TxEnv { + gas_limit: tx.gas_limit, + gas_price: tx.gas_price, + gas_priority_fee: None, + kind: tx.to, + value: tx.value, + data: tx.input.clone(), + chain_id: Some(tx.chain_id), + nonce: tx.nonce, + access_list: tx.access_list.clone(), + blob_hashes: Default::default(), + max_fee_per_blob_gas: Default::default(), + authorization_list: Default::default(), + tx_type: 1, + caller: sender, + }, + ScrollTypedTransaction::Eip1559(tx) => TxEnv { + gas_limit: tx.gas_limit, + gas_price: tx.max_fee_per_gas, + gas_priority_fee: Some(tx.max_priority_fee_per_gas), + kind: tx.to, + value: tx.value, + data: tx.input.clone(), + chain_id: Some(tx.chain_id), + nonce: tx.nonce, + access_list: tx.access_list.clone(), + blob_hashes: Default::default(), + max_fee_per_blob_gas: Default::default(), + authorization_list: Default::default(), + tx_type: 2, + caller: sender, + }, + ScrollTypedTransaction::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 rlp_bytes = (!tx.is_l1_message()).then_some(envelope.into()); + Self::new(base, rlp_bytes) + } +} + +/// Bincode-compatible transaction type serde implementations. +#[cfg(feature = "serde-bincode-compat")] +pub mod serde_bincode_compat { + use alloc::borrow::Cow; + use alloy_consensus::transaction::serde_bincode_compat::{TxEip1559, TxEip2930, TxLegacy}; + use alloy_primitives::{Signature, TxHash}; + use reth_primitives_traits::{serde_bincode_compat::SerdeBincodeCompat, SignedTransaction}; + use serde::{Deserialize, Serialize}; + + /// Bincode-compatible [`super::ScrollTypedTransaction`] serde implementation. + #[derive(Debug, Serialize, Deserialize)] + #[allow(missing_docs)] + enum ScrollTypedTransaction<'a> { + Legacy(TxLegacy<'a>), + Eip2930(TxEip2930<'a>), + Eip1559(TxEip1559<'a>), + L1Message(Cow<'a, scroll_alloy_consensus::TxL1Message>), + } + + impl<'a> From<&'a super::ScrollTypedTransaction> for ScrollTypedTransaction<'a> { + fn from(value: &'a super::ScrollTypedTransaction) -> Self { + match value { + super::ScrollTypedTransaction::Legacy(tx) => Self::Legacy(TxLegacy::from(tx)), + super::ScrollTypedTransaction::Eip2930(tx) => Self::Eip2930(TxEip2930::from(tx)), + super::ScrollTypedTransaction::Eip1559(tx) => Self::Eip1559(TxEip1559::from(tx)), + super::ScrollTypedTransaction::L1Message(tx) => Self::L1Message(Cow::Borrowed(tx)), + } + } + } + + impl<'a> From<ScrollTypedTransaction<'a>> for super::ScrollTypedTransaction { + fn from(value: ScrollTypedTransaction<'a>) -> Self { + match value { + ScrollTypedTransaction::Legacy(tx) => Self::Legacy(tx.into()), + ScrollTypedTransaction::Eip2930(tx) => Self::Eip2930(tx.into()), + ScrollTypedTransaction::Eip1559(tx) => Self::Eip1559(tx.into()), + ScrollTypedTransaction::L1Message(tx) => Self::L1Message(tx.into_owned()), + } + } + } + + /// Bincode-compatible [`super::ScrollTransactionSigned`] serde implementation. + #[derive(Debug, Serialize, Deserialize)] + pub struct ScrollTransactionSigned<'a> { + hash: TxHash, + signature: Signature, + transaction: ScrollTypedTransaction<'a>, + } + + impl<'a> From<&'a super::ScrollTransactionSigned> for ScrollTransactionSigned<'a> { + fn from(value: &'a super::ScrollTransactionSigned) -> Self { + Self { + hash: *value.tx_hash(), + signature: value.signature, + transaction: ScrollTypedTransaction::from(&value.transaction), + } + } + } + + impl<'a> From<ScrollTransactionSigned<'a>> for super::ScrollTransactionSigned { + fn from(value: ScrollTransactionSigned<'a>) -> Self { + Self { + hash: value.hash.into(), + signature: value.signature, + transaction: value.transaction.into(), + } + } + } + + impl SerdeBincodeCompat for super::ScrollTransactionSigned { + type BincodeRepr<'a> = ScrollTransactionSigned<'a>; + + fn as_repr(&self) -> Self::BincodeRepr<'_> { + self.into() + } + + fn from_repr(repr: Self::BincodeRepr<'_>) -> Self { + repr.into() + } + } +}
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..a7b39f696fd254513dd54852a4c6ffe27cd26374 --- /dev/null +++ scroll-reth/crates/scroll/rpc/Cargo.toml @@ -0,0 +1,54 @@ +[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-evm.workspace = true +reth-primitives.workspace = true +reth-primitives-traits.workspace = true +reth-provider.workspace = true +reth-rpc-eth-api.workspace = true +reth-rpc-eth-types.workspace = true +reth-tasks = { workspace = true, features = ["rayon"] } +reth-transaction-pool.workspace = true +reth-rpc.workspace = true +reth-node-api.workspace = true +reth-node-builder.workspace = true +reth-network-api.workspace = true +reth-chainspec.workspace = true + +# scroll +reth-scroll-chainspec.workspace = true +reth-scroll-primitives = { workspace = true, features = ["serde", "serde-bincode-compat", "reth-codec"] } +scroll-alloy-consensus.workspace = true +scroll-alloy-evm.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 +revm.workspace = true + +# async +parking_lot.workspace = true +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..5f55b5d4fa9c5eb61c6ddd1efeff3fd715954ba3 --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/error.rs @@ -0,0 +1,51 @@ +//! RPC errors specific to Scroll. + +use alloy_rpc_types_eth::BlockError; +use reth_rpc_eth_api::AsEthApiError; +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), +} + +impl AsEthApiError for ScrollEthApiError { + fn as_err(&self) -> Option<&EthApiError> { + match self { + Self::Eth(err) => Some(err), + } + } +} + +impl From<ScrollEthApiError> for jsonrpsee_types::error::ErrorObject<'static> { + fn from(err: ScrollEthApiError) -> Self { + match err { + ScrollEthApiError::Eth(err) => err.into(), + } + } +} + +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() + } +}
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..ccbc518ddd591873c73cea59f6f4fd3095a7870f --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/eth/block.rs @@ -0,0 +1,76 @@ +//! Loads and formats Scroll block RPC response. + +use crate::{eth::ScrollNodeCore, ScrollEthApi, ScrollEthApiError, ScrollReceiptBuilder}; + +use alloy_consensus::BlockHeader; +use alloy_rpc_types_eth::BlockId; +use reth_chainspec::ChainSpecProvider; +use reth_node_api::BlockBody; +use reth_primitives::TransactionMeta; +use reth_primitives_traits::SignedTransaction; +use reth_provider::{BlockReader, HeaderProvider}; +use reth_rpc_eth_api::{ + helpers::{EthBlocks, LoadBlock, LoadPendingBlock, LoadReceipt, SpawnBlocking}, + types::RpcTypes, + RpcReceipt, +}; +use reth_scroll_chainspec::ScrollChainSpec; +use reth_scroll_primitives::{ScrollReceipt, ScrollTransactionSigned}; +use scroll_alloy_rpc_types::ScrollTransactionReceipt; + +impl<N> EthBlocks for ScrollEthApi<N> +where + Self: LoadBlock< + Error = ScrollEthApiError, + NetworkTypes: RpcTypes<Receipt = ScrollTransactionReceipt>, + Provider: BlockReader<Receipt = ScrollReceipt, Transaction = ScrollTransactionSigned>, + >, + N: ScrollNodeCore<Provider: ChainSpecProvider<ChainSpec = ScrollChainSpec> + HeaderProvider>, +{ + async fn block_receipts( + &self, + block_id: BlockId, + ) -> Result<Option<Vec<RpcReceipt<Self::NetworkTypes>>>, Self::Error> + where + Self: LoadReceipt, + { + if let Some((block, receipts)) = self.load_block_and_receipts(block_id).await? { + let block_number = block.number(); + let base_fee = block.base_fee_per_gas(); + let block_hash = block.hash(); + let excess_blob_gas = block.excess_blob_gas(); + let timestamp = block.timestamp(); + + return block + .body() + .transactions() + .iter() + .zip(receipts.iter()) + .enumerate() + .map(|(idx, (tx, receipt))| -> Result<_, _> { + let meta = TransactionMeta { + tx_hash: *tx.tx_hash(), + index: idx as u64, + block_hash, + block_number, + base_fee, + excess_blob_gas, + timestamp, + }; + ScrollReceiptBuilder::new(tx, meta, receipt, &receipts) + .map(|builder| builder.build()) + }) + .collect::<Result<Vec<_>, Self::Error>>() + .map(Some) + } + + Ok(None) + } +} + +impl<N> LoadBlock for ScrollEthApi<N> +where + Self: LoadPendingBlock + SpawnBlocking, + N: ScrollNodeCore, +{ +}
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..476482672f7e2ffc35b74b71eed9add2e2fe666b --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/eth/call.rs @@ -0,0 +1,152 @@ +use super::ScrollNodeCore; +use crate::{ScrollEthApi, ScrollEthApiError}; + +use alloy_primitives::{TxKind, U256}; +use alloy_rpc_types_eth::transaction::TransactionRequest; +use reth_evm::{block::BlockExecutorFactory, ConfigureEvm, EvmEnv, EvmFactory, SpecFor}; +use reth_primitives_traits::NodePrimitives; +use reth_provider::{ProviderHeader, ProviderTx}; +use reth_rpc_eth_api::{ + helpers::{estimate::EstimateCall, Call, EthCall, LoadBlock, LoadState, SpawnBlocking}, + FromEthApiError, FullEthApiTypes, IntoEthApiError, +}; +use reth_rpc_eth_types::{ + error::FromEvmError, revm_utils::CallFees, EthApiError, RpcInvalidTransactionError, +}; +use revm::{ + context::{Block, TxEnv}, + Database, +}; +use scroll_alloy_evm::ScrollTransactionIntoTxEnv; + +impl<N> EthCall for ScrollEthApi<N> +where + Self: EstimateCall + LoadBlock + FullEthApiTypes, + N: ScrollNodeCore, +{ +} + +impl<N> EstimateCall for ScrollEthApi<N> +where + Self: Call, + Self::Error: From<ScrollEthApiError>, + N: ScrollNodeCore, +{ +} + +impl<N> Call for ScrollEthApi<N> +where + Self: LoadState< + Evm: ConfigureEvm< + Primitives: NodePrimitives< + BlockHeader = ProviderHeader<Self::Provider>, + SignedTx = ProviderTx<Self::Provider>, + >, + BlockExecutorFactory: BlockExecutorFactory< + EvmFactory: EvmFactory<Tx = ScrollTransactionIntoTxEnv<TxEnv>>, + >, + >, + Error: FromEvmError<Self::Evm>, + > + SpawnBlocking, + Self::Error: From<ScrollEthApiError>, + N: ScrollNodeCore, +{ + #[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() + } + + fn create_txn_env( + &self, + evm_env: &EvmEnv<SpecFor<Self::Evm>>, + request: TransactionRequest, + mut db: impl Database<Error: Into<EthApiError>>, + ) -> Result<ScrollTransactionIntoTxEnv<TxEnv>, Self::Error> { + // Ensure that if versioned hashes are set, they're not empty + if request.blob_versioned_hashes.as_ref().is_some_and(|hashes| hashes.is_empty()) { + return Err(RpcInvalidTransactionError::BlobTransactionMissingBlobHashes.into_eth_err()) + } + + let tx_type = request.preferred_type() as u8; + + let TransactionRequest { + from, + to, + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + gas, + value, + input, + nonce, + access_list, + chain_id, + blob_versioned_hashes, + max_fee_per_blob_gas, + authorization_list, + transaction_type: _, + sidecar: _, + } = request; + + let CallFees { max_priority_fee_per_gas, gas_price, max_fee_per_blob_gas } = + CallFees::ensure_fees( + gas_price.map(U256::from), + max_fee_per_gas.map(U256::from), + max_priority_fee_per_gas.map(U256::from), + U256::from(evm_env.block_env.basefee), + blob_versioned_hashes.as_deref(), + max_fee_per_blob_gas.map(U256::from), + evm_env.block_env.blob_gasprice().map(U256::from), + )?; + + let gas_limit = gas.unwrap_or( + // Use maximum allowed gas limit. The reason for this + // is that both Erigon and Geth use pre-configured gas cap even if + // it's possible to derive the gas limit from the block: + // <https://github.com/ledgerwatch/erigon/blob/eae2d9a79cb70dbe30b3a6b79c436872e4605458/cmd/rpcdaemon/commands/trace_adhoc.go#L956 + // https://github.com/ledgerwatch/erigon/blob/eae2d9a79cb70dbe30b3a6b79c436872e4605458/eth/ethconfig/config.go#L94> + evm_env.block_env.gas_limit, + ); + + let chain_id = chain_id.unwrap_or(evm_env.cfg_env.chain_id); + + let caller = from.unwrap_or_default(); + + let nonce = if let Some(nonce) = nonce { + nonce + } else { + db.basic(caller).map_err(Into::into)?.map(|acc| acc.nonce).unwrap_or_default() + }; + + let base = TxEnv { + tx_type, + gas_limit, + nonce, + caller, + gas_price: gas_price.saturating_to(), + gas_priority_fee: max_priority_fee_per_gas.map(|v| v.saturating_to()), + kind: to.unwrap_or(TxKind::Create), + value: value.unwrap_or_default(), + data: input + .try_into_unique_input() + .map_err(Self::Error::from_eth_err)? + .unwrap_or_default(), + chain_id: Some(chain_id), + access_list: access_list.unwrap_or_default(), + // EIP-4844 fields + blob_hashes: blob_versioned_hashes.unwrap_or_default(), + max_fee_per_blob_gas: max_fee_per_blob_gas + .map(|v| v.saturating_to()) + .unwrap_or_default(), + // EIP-7702 fields + authorization_list: authorization_list.unwrap_or_default(), + }; + + Ok(ScrollTransactionIntoTxEnv::new(base, Some(Default::default()))) + } +}
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..c27eb729c5f2e3aa65f0322a965ce8f46b96bac8 --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/eth/mod.rs @@ -0,0 +1,325 @@ +//! Scroll-Reth `eth_` endpoint implementation. + +use std::{fmt, sync::Arc}; + +use alloy_primitives::U256; +use reth_chainspec::{EthChainSpec, EthereumHardforks}; +use reth_evm::ConfigureEvm; +use reth_network_api::NetworkInfo; +use reth_node_api::FullNodeComponents; +use reth_provider::{ + BlockNumReader, BlockReader, BlockReaderIdExt, CanonStateSubscriptions, ChainSpecProvider, + ProviderBlock, ProviderHeader, ProviderReceipt, ProviderTx, StageCheckpointReader, + StateProviderFactory, +}; +use reth_rpc::eth::{core::EthApiInner, DevSigner}; +use reth_rpc_eth_api::{ + helpers::{ + AddDevSigners, EthApiSpec, EthFees, EthSigner, EthState, LoadBlock, LoadFee, LoadState, + SpawnBlocking, Trace, + }, + EthApiTypes, FullEthApiServer, RpcNodeCore, RpcNodeCoreExt, +}; +use reth_rpc_eth_types::{EthStateCache, FeeHistoryCache, GasPriceOracle}; +use reth_tasks::{ + pool::{BlockingTaskGuard, BlockingTaskPool}, + TaskSpawner, +}; +use reth_transaction_pool::TransactionPool; + +pub use receipt::ScrollReceiptBuilder; +use reth_node_builder::rpc::{EthApiBuilder, EthApiCtx}; +use reth_primitives_traits::NodePrimitives; +use reth_rpc_eth_types::error::FromEvmError; +use reth_scroll_primitives::ScrollPrimitives; +use scroll_alloy_network::Scroll; + +use crate::ScrollEthApiError; + +mod block; +mod call; +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> = EthApiInner< + <N as RpcNodeCore>::Provider, + <N as RpcNodeCore>::Pool, + <N as RpcNodeCore>::Network, + <N as RpcNodeCore>::Evm, +>; + +/// 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: ScrollNodeCore> { + /// Gateway to node's core components. + inner: Arc<ScrollEthApiInner<N>>, +} + +impl<N> ScrollEthApi<N> +where + N: ScrollNodeCore< + Provider: BlockReaderIdExt + + ChainSpecProvider + + CanonStateSubscriptions<Primitives = ScrollPrimitives> + + Clone + + 'static, + >, +{ + /// Returns a reference to the [`EthApiNodeBackend`]. + pub fn eth_api(&self) -> &EthApiNodeBackend<N> { + self.inner.eth_api() + } + + /// Return a builder for the [`ScrollEthApi`]. + pub const fn builder() -> ScrollEthApiBuilder { + ScrollEthApiBuilder::new() + } +} + +impl<N> EthApiTypes for ScrollEthApi<N> +where + Self: Send + Sync, + N: ScrollNodeCore, +{ + type Error = ScrollEthApiError; + type NetworkTypes = Scroll; + type TransactionCompat = Self; + + fn tx_resp_builder(&self) -> &Self::TransactionCompat { + self + } +} + +impl<N> RpcNodeCore for ScrollEthApi<N> +where + N: ScrollNodeCore, +{ + type Primitives = N::Primitives; + type Provider = N::Provider; + type Pool = N::Pool; + type Evm = <N as RpcNodeCore>::Evm; + type Network = <N as RpcNodeCore>::Network; + type PayloadBuilder = (); + + #[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 payload_builder(&self) -> &Self::PayloadBuilder { + &() + } + + #[inline] + fn provider(&self) -> &Self::Provider { + self.inner.eth_api.provider() + } +} + +impl<N> RpcNodeCoreExt for ScrollEthApi<N> +where + N: ScrollNodeCore, +{ + #[inline] + fn cache(&self) -> &EthStateCache<ProviderBlock<N::Provider>, ProviderReceipt<N::Provider>> { + self.inner.eth_api.cache() + } +} + +impl<N> EthApiSpec for ScrollEthApi<N> +where + N: ScrollNodeCore< + Provider: ChainSpecProvider<ChainSpec: EthereumHardforks> + + BlockNumReader + + StageCheckpointReader, + Network: NetworkInfo, + >, +{ + type Transaction = ProviderTx<Self::Provider>; + + #[inline] + fn starting_block(&self) -> U256 { + self.inner.eth_api.starting_block() + } + + #[inline] + fn signers(&self) -> &parking_lot::RwLock<Vec<Box<dyn EthSigner<ProviderTx<Self::Provider>>>>> { + self.inner.eth_api.signers() + } +} + +impl<N> SpawnBlocking for ScrollEthApi<N> +where + Self: Send + Sync + Clone + 'static, + N: ScrollNodeCore, +{ + #[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> LoadFee for ScrollEthApi<N> +where + Self: LoadBlock<Provider = N::Provider>, + N: ScrollNodeCore< + Provider: BlockReaderIdExt + + ChainSpecProvider<ChainSpec: EthChainSpec + EthereumHardforks> + + StateProviderFactory, + >, +{ + #[inline] + fn gas_oracle(&self) -> &GasPriceOracle<Self::Provider> { + self.inner.eth_api.gas_oracle() + } + + #[inline] + fn fee_history_cache(&self) -> &FeeHistoryCache { + self.inner.eth_api.fee_history_cache() + } +} + +impl<N> LoadState for ScrollEthApi<N> where + N: ScrollNodeCore< + Provider: StateProviderFactory + ChainSpecProvider<ChainSpec: EthereumHardforks>, + Pool: TransactionPool, + > +{ +} + +impl<N> EthState for ScrollEthApi<N> +where + Self: LoadState + SpawnBlocking, + N: ScrollNodeCore, +{ + #[inline] + fn max_proof_window(&self) -> u64 { + self.inner.eth_api.eth_proof_window() + } +} + +impl<N> EthFees for ScrollEthApi<N> +where + Self: LoadFee, + N: ScrollNodeCore, +{ +} + +impl<N> Trace for ScrollEthApi<N> +where + Self: RpcNodeCore<Provider: BlockReader> + + LoadState< + Evm: ConfigureEvm< + Primitives: NodePrimitives< + BlockHeader = ProviderHeader<Self::Provider>, + SignedTx = ProviderTx<Self::Provider>, + >, + >, + Error: FromEvmError<Self::Evm>, + >, + N: ScrollNodeCore, +{ +} + +impl<N> AddDevSigners for ScrollEthApi<N> +where + N: ScrollNodeCore, +{ + fn with_dev_accounts(&self) { + *self.inner.eth_api.signers().write() = DevSigner::random_signers(20) + } +} + +impl<N: ScrollNodeCore> fmt::Debug for ScrollEthApi<N> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ScrollEthApi").finish_non_exhaustive() + } +} + +/// Container type `ScrollEthApi` +#[allow(missing_debug_implementations)] +struct ScrollEthApiInner<N: ScrollNodeCore> { + /// Gateway to node's core components. + eth_api: EthApiNodeBackend<N>, +} + +impl<N: ScrollNodeCore> ScrollEthApiInner<N> { + /// Returns a reference to the [`EthApiNodeBackend`]. + const fn eth_api(&self) -> &EthApiNodeBackend<N> { + &self.eth_api + } +} + +/// A type that knows how to build a [`ScrollEthApi`]. +#[derive(Debug, Default)] +pub struct ScrollEthApiBuilder {} + +impl ScrollEthApiBuilder { + /// Creates a [`ScrollEthApiBuilder`] instance. + pub const fn new() -> Self { + Self {} + } +} + +impl<N> EthApiBuilder<N> for ScrollEthApiBuilder +where + N: FullNodeComponents, + ScrollEthApi<N>: FullEthApiServer<Provider = N::Provider, Pool = N::Pool>, +{ + type EthApi = ScrollEthApi<N>; + + async fn build_eth_api(self, ctx: EthApiCtx<'_, N>) -> eyre::Result<Self::EthApi> { + let eth_api = reth_rpc::EthApiBuilder::new( + ctx.components.provider().clone(), + ctx.components.pool().clone(), + ctx.components.network().clone(), + ctx.components.evm_config().clone(), + ) + .eth_cache(ctx.cache) + .task_spawner(ctx.components.task_executor().clone()) + .gas_cap(ctx.config.rpc_gas_cap.into()) + .max_simulate_blocks(ctx.config.rpc_max_simulate_blocks) + .eth_proof_window(ctx.config.eth_proof_window) + .fee_history_cache_config(ctx.config.fee_history_cache) + .proof_permits(ctx.config.proof_permits) + .build_inner(); + + Ok(ScrollEthApi { inner: Arc::new(ScrollEthApiInner { eth_api }) }) + } +}
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..d680a3f65cfc49cd4bc510c1456d081f7f1d323c --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/eth/pending_block.rs @@ -0,0 +1,75 @@ +//! Loads Scroll pending block for an RPC response. + +use crate::ScrollEthApi; + +use alloy_consensus::{BlockHeader, Header}; +use alloy_primitives::B256; +use reth_chainspec::EthChainSpec; +use reth_evm::{ConfigureEvm, NextBlockEnvAttributes}; +use reth_primitives_traits::{NodePrimitives, SealedHeader}; +use reth_provider::{ + BlockReaderIdExt, ChainSpecProvider, ProviderBlock, ProviderHeader, ProviderReceipt, + ProviderTx, StateProviderFactory, +}; +use reth_rpc_eth_api::{ + helpers::{LoadPendingBlock, SpawnBlocking}, + types::RpcTypes, + EthApiTypes, RpcNodeCore, +}; +use reth_rpc_eth_types::{error::FromEvmError, PendingBlock}; +use reth_scroll_primitives::{ScrollBlock, ScrollReceipt, ScrollTransactionSigned}; +use reth_transaction_pool::{PoolTransaction, TransactionPool}; +use scroll_alloy_hardforks::ScrollHardforks; + +impl<N> LoadPendingBlock for ScrollEthApi<N> +where + Self: SpawnBlocking + + EthApiTypes< + NetworkTypes: RpcTypes< + Header = alloy_rpc_types_eth::Header<ProviderHeader<Self::Provider>>, + >, + Error: FromEvmError<Self::Evm>, + >, + N: RpcNodeCore< + Provider: BlockReaderIdExt< + Transaction = ScrollTransactionSigned, + Block = ScrollBlock, + Receipt = ScrollReceipt, + Header = Header, + > + ChainSpecProvider<ChainSpec: EthChainSpec + ScrollHardforks> + + StateProviderFactory, + Pool: TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<N::Provider>>>, + Evm: ConfigureEvm< + Primitives: NodePrimitives< + SignedTx = ProviderTx<Self::Provider>, + BlockHeader = ProviderHeader<Self::Provider>, + Receipt = ProviderReceipt<Self::Provider>, + Block = ProviderBlock<Self::Provider>, + >, + NextBlockEnvCtx = NextBlockEnvAttributes, + >, + >, +{ + #[inline] + fn pending_block( + &self, + ) -> &tokio::sync::Mutex< + Option<PendingBlock<ProviderBlock<Self::Provider>, ProviderReceipt<Self::Provider>>>, + > { + self.inner.eth_api.pending_block() + } + + fn next_env_attributes( + &self, + parent: &SealedHeader<ProviderHeader<Self::Provider>>, + ) -> Result<<Self::Evm as ConfigureEvm>::NextBlockEnvCtx, Self::Error> { + Ok(NextBlockEnvAttributes { + timestamp: parent.timestamp().saturating_add(12), + suggested_fee_recipient: parent.beneficiary(), + prev_randao: B256::random(), + gas_limit: parent.gas_limit(), + parent_beacon_block_root: None, + withdrawals: None, + }) + } +}
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..f83f58eca904dd209129deed7ec2891a9fe72c71 --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/eth/receipt.rs @@ -0,0 +1,94 @@ +//! Loads and formats Scroll receipt RPC response. + +use crate::{ScrollEthApi, ScrollEthApiError}; +use alloy_rpc_types_eth::{Log, TransactionReceipt}; +use reth_node_api::{FullNodeComponents, NodeTypes}; +use reth_primitives::TransactionMeta; +use reth_provider::{ReceiptProvider, TransactionsProvider}; +use reth_rpc_eth_api::{helpers::LoadReceipt, FromEthApiError, RpcReceipt}; +use reth_rpc_eth_types::{receipt::build_receipt, EthApiError}; + +use reth_scroll_chainspec::ScrollChainSpec; +use reth_scroll_primitives::{ScrollReceipt, ScrollTransactionSigned}; +use scroll_alloy_consensus::ScrollReceiptEnvelope; +use scroll_alloy_rpc_types::{ScrollTransactionReceipt, ScrollTransactionReceiptFields}; + +impl<N> LoadReceipt for ScrollEthApi<N> +where + Self: Send + Sync, + N: FullNodeComponents<Types: NodeTypes<ChainSpec = ScrollChainSpec>>, + Self::Provider: TransactionsProvider<Transaction = ScrollTransactionSigned> + + ReceiptProvider<Receipt = ScrollReceipt>, +{ + async fn build_transaction_receipt( + &self, + tx: ScrollTransactionSigned, + meta: TransactionMeta, + receipt: ScrollReceipt, + ) -> Result<RpcReceipt<Self::NetworkTypes>, Self::Error> { + let all_receipts = self + .inner + .eth_api + .cache() + .get_receipts(meta.block_hash) + .await + .map_err(Self::Error::from_eth_err)? + .ok_or(Self::Error::from_eth_err(EthApiError::HeaderNotFound( + meta.block_hash.into(), + )))?; + + Ok(ScrollReceiptBuilder::new(&tx, meta, &receipt, &all_receipts)?.build()) + } +} + +/// 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( + transaction: &ScrollTransactionSigned, + meta: TransactionMeta, + receipt: &ScrollReceipt, + all_receipts: &[ScrollReceipt], + ) -> Result<Self, ScrollEthApiError> { + let core_receipt = + build_receipt(transaction, meta, receipt, all_receipts, None, |receipt_with_bloom| { + match receipt { + 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::L1Message(_) => { + ScrollReceiptEnvelope::<Log>::L1Message(receipt_with_bloom) + } + } + })?; + + let scroll_receipt_fields = + ScrollTransactionReceiptFields { l1_fee: Some(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..31aa8c94a410a1992ff7228e35d4037644c56d1e --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/eth/transaction.rs @@ -0,0 +1,153 @@ +//! Loads and formats Scroll transaction RPC response. + +use alloy_consensus::{Signed, Transaction as _}; +use alloy_primitives::{Bytes, Sealable, Sealed, Signature, B256}; +use alloy_rpc_types_eth::TransactionInfo; +use reth_node_api::FullNodeComponents; +use reth_primitives::Recovered; +use reth_primitives_traits::SignedTransaction; +use reth_provider::{ + BlockReader, BlockReaderIdExt, ProviderTx, ReceiptProvider, TransactionsProvider, +}; +use reth_rpc_eth_api::{ + helpers::{EthSigner, EthTransactions, LoadTransaction, SpawnBlocking}, + FromEthApiError, FullEthApiTypes, RpcNodeCore, RpcNodeCoreExt, TransactionCompat, +}; +use reth_rpc_eth_types::{utils::recover_raw_transaction, EthApiError}; +use reth_scroll_primitives::{ScrollReceipt, ScrollTransactionSigned}; +use reth_transaction_pool::{PoolTransaction, TransactionOrigin, TransactionPool}; + +use scroll_alloy_consensus::{ScrollTxEnvelope, ScrollTypedTransaction}; +use scroll_alloy_rpc_types::{ScrollTransactionRequest, Transaction}; + +use crate::{eth::ScrollNodeCore, ScrollEthApi, ScrollEthApiError}; + +impl<N> EthTransactions for ScrollEthApi<N> +where + Self: LoadTransaction<Provider: BlockReaderIdExt>, + N: ScrollNodeCore<Provider: BlockReader<Transaction = ProviderTx<Self::Provider>>>, +{ + fn signers(&self) -> &parking_lot::RwLock<Vec<Box<dyn EthSigner<ProviderTx<Self::Provider>>>>> { + 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 hash = self + .pool() + .add_transaction(TransactionOrigin::Local, pool_transaction) + .await + .map_err(Self::Error::from_eth_err)?; + + Ok(hash) + } +} + +impl<N> LoadTransaction for ScrollEthApi<N> +where + Self: SpawnBlocking + FullEthApiTypes + RpcNodeCoreExt, + N: ScrollNodeCore<Provider: TransactionsProvider, Pool: TransactionPool>, + Self::Pool: TransactionPool, +{ +} + +impl<N> TransactionCompat<ScrollTransactionSigned> for ScrollEthApi<N> +where + N: FullNodeComponents<Provider: ReceiptProvider<Receipt = ScrollReceipt>>, +{ + type Transaction = Transaction; + type Error = ScrollEthApiError; + + fn fill( + &self, + tx: Recovered<ScrollTransactionSigned>, + tx_info: TransactionInfo, + ) -> Result<Self::Transaction, Self::Error> { + let from = tx.signer(); + let hash = *tx.tx_hash(); + let ScrollTransactionSigned { transaction, signature, .. } = tx.into_inner(); + + let inner = match transaction { + ScrollTypedTransaction::Legacy(tx) => Signed::new_unchecked(tx, signature, hash).into(), + ScrollTypedTransaction::Eip2930(tx) => { + Signed::new_unchecked(tx, signature, hash).into() + } + ScrollTypedTransaction::Eip1559(tx) => { + Signed::new_unchecked(tx, signature, hash).into() + } + ScrollTypedTransaction::L1Message(tx) => { + ScrollTxEnvelope::L1Message(tx.seal_unchecked(hash)) + } + }; + + let TransactionInfo { + block_hash, block_number, index: transaction_index, base_fee, .. + } = tx_info; + + let effective_gas_price = if inner.is_l1_message() { + // For l1 message, we must always set the `gasPrice` field to 0 in rpc + // l1 message tx don't have a gas price field, but serde of `Transaction` will take care + // of it + 0 + } else { + base_fee + .map(|base_fee| { + inner.effective_tip_per_gas(base_fee).unwrap_or_default() + base_fee as u128 + }) + .unwrap_or_else(|| inner.max_fee_per_gas()) + }; + let inner = Recovered::new_unchecked(inner, from); + + Ok(Transaction { + inner: alloy_rpc_types_eth::Transaction { + inner, + block_hash, + block_number, + transaction_index, + effective_gas_price: Some(effective_gas_price), + }, + }) + } + + fn build_simulate_v1_transaction( + &self, + request: alloy_rpc_types_eth::TransactionRequest, + ) -> Result<ScrollTransactionSigned, Self::Error> { + let request: ScrollTransactionRequest = request.into(); + let Ok(tx) = request.build_typed_tx() else { + return Err(ScrollEthApiError::Eth(EthApiError::TransactionConversionError)) + }; + + // Create an empty signature for the transaction. + let signature = Signature::new(Default::default(), Default::default(), false); + Ok(ScrollTransactionSigned::new_unhashed(tx, signature)) + } + + fn otterscan_api_truncate_input(tx: &mut Self::Transaction) { + let mut tx = tx.inner.inner.inner_mut(); + let input = match &mut tx { + ScrollTxEnvelope::Eip1559(tx) => &mut tx.tx_mut().input, + ScrollTxEnvelope::Eip2930(tx) => &mut tx.tx_mut().input, + ScrollTxEnvelope::Legacy(tx) => &mut tx.tx_mut().input, + ScrollTxEnvelope::L1Message(tx) => { + let (mut deposit, hash) = std::mem::replace( + tx, + Sealed::new_unchecked(Default::default(), Default::default()), + ) + .split(); + deposit.input = deposit.input.slice(..4); + let mut deposit = deposit.seal_unchecked(hash); + std::mem::swap(tx, &mut deposit); + return + } + _ => return, + }; + *input = input.slice(..4); + } +}
diff --git reth/crates/scroll/rpc/src/lib.rs scroll-reth/crates/scroll/rpc/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..a3058ffee023922679eeb8fa951f3294c2c60096 --- /dev/null +++ scroll-reth/crates/scroll/rpc/src/lib.rs @@ -0,0 +1,15 @@ +//! 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 use error::ScrollEthApiError; +pub use eth::{ScrollEthApi, ScrollReceiptBuilder};
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..844dffe7a5fe7fd3c78acb8a7f4948fee5a34036 --- /dev/null +++ scroll-reth/crates/scroll/txpool/Cargo.toml @@ -0,0 +1,52 @@ +[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 +alloy-rpc-types-eth.workspace = true + +# reth +reth-chain-state.workspace = true +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-evm.workspace = true +reth-scroll-forks.workspace = true +reth-scroll-primitives.workspace = true + +# scroll-alloy +scroll-alloy-consensus.workspace = true + +# metrics +reth-metrics.workspace = true +metrics.workspace = true + +# misc +c-kzg.workspace = true +derive_more.workspace = true +futures-util.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..921ec74ff2d59bc72b313f048be73f48bd36fe46 --- /dev/null +++ scroll-reth/crates/scroll/txpool/src/transaction.rs @@ -0,0 +1,252 @@ +use alloy_consensus::{ + transaction::Recovered, BlobTransactionSidecar, BlobTransactionValidationError, Typed2718, +}; +use alloy_eips::{eip2930::AccessList, 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 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<BlobTransactionSidecar>, + ) -> Option<Recovered<Self::Pooled>> { + None + } + + fn try_from_eip4844( + _tx: Recovered<Self::Consensus>, + _sidecar: BlobTransactionSidecar, + ) -> Option<Self> { + None + } + + fn validate_blob( + &self, + _sidecar: &BlobTransactionSidecar, + _settings: &KzgSettings, + ) -> Result<(), BlobTransactionValidationError> { + Err(BlobTransactionValidationError::NotBlobTransaction(self.ty())) + } +} + +#[cfg(test)] +mod tests { + use crate::{ScrollPooledTransaction, ScrollTransactionValidator}; + use alloy_consensus::transaction::Recovered; + 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::new_unhashed(deposit_tx, signature); + 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..1a0f79a4215d6012cee2a53877f80bccaf44e91f --- /dev/null +++ scroll-reth/crates/scroll/txpool/src/validator.rs @@ -0,0 +1,244 @@ +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_evm::{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 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, +{ + /// 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::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, + } = 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 cost_addition = match l1_block_info.l1_tx_data_fee( + self.chain_spec(), + self.block_timestamp(), + self.block_number(), + &encoded, + false, + ) { + Ok(cost) => cost, + Err(err) => { + return TransactionValidationOutcome::Error(*valid_tx.hash(), Box::new(err)) + } + }; + 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, + transaction: valid_tx, + propagate, + } + } + + 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, +{ + 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/debug_cmd/build_block.rs scroll-reth/crates/ethereum/cli/src/debug_cmd/build_block.rs index 8942dd27ace4089a9425c52ffe2f2c304a6936ab..7e975b652c8e2986842dc7116486e84b2564e54b 100644 --- reth/crates/ethereum/cli/src/debug_cmd/build_block.rs +++ scroll-reth/crates/ethereum/cli/src/debug_cmd/build_block.rs @@ -35,8 +35,6 @@ use reth_transaction_pool::{ blobstore::InMemoryBlobStore, BlobStore, EthPooledTransaction, PoolConfig, TransactionOrigin, TransactionPool, TransactionValidationTaskExecutor, }; -use reth_trie::StateRoot; -use reth_trie_db::DatabaseStateRoot; use std::{path::PathBuf, str::FromStr, sync::Arc}; use tracing::*;   @@ -234,10 +232,8 @@ ExecutionOutcome::from((block_execution_output, block.number)); debug!(target: "reth::cli", ?execution_outcome, "Executed block");   let hashed_post_state = state_provider.hashed_post_state(execution_outcome.state()); - let (state_root, trie_updates) = StateRoot::overlay_root_with_updates( - provider_factory.provider()?.tx_ref(), - hashed_post_state.clone(), - )?; + let (state_root, trie_updates) = + state_provider.state_root_with_updates(hashed_post_state.clone())?;   if state_root != block_with_senders.state_root() { eyre::bail!(
diff --git reth/crates/ethereum/cli/src/debug_cmd/merkle.rs scroll-reth/crates/ethereum/cli/src/debug_cmd/merkle.rs index 78ce6db8fb427e41ee27670f2f7210dfea3ec52c..673f06b08f998a3946105bab623cc6a676708d92 100644 --- reth/crates/ethereum/cli/src/debug_cmd/merkle.rs +++ scroll-reth/crates/ethereum/cli/src/debug_cmd/merkle.rs @@ -130,7 +130,7 @@ // build the full block client let consensus: Arc<dyn Consensus<BlockTy<N>, Error = ConsensusError>> = Arc::new(EthBeaconConsensus::new(provider_factory.chain_spec())); - let block_range_client = FullBlockClient::new(fetch_client, consensus); + let block_range_client = FullBlockClient::new(fetch_client, consensus.clone());   // get best block number let best_block_number = provider_rw.best_block_number()?; @@ -145,7 +145,8 @@ .await;   let mut account_hashing_stage = AccountHashingStage::default(); let mut storage_hashing_stage = StorageHashingStage::default(); - let mut merkle_stage = MerkleStage::default_execution(); + let mut merkle_stage = + MerkleStage::<N::Primitives>::default_execution_with_consensus(consensus);   for block in blocks.into_iter().rev() { let block_number = block.number;
diff --git reth/crates/ethereum/consensus/src/lib.rs scroll-reth/crates/ethereum/consensus/src/lib.rs index 89ce9f72e0dc7c0ff1d3d6be7a5dd17ef81704a9..70eb799311e7651f97e120a595ed06f13a5eb06d 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 726d7464d525f2e12ebc17816dd4f45e67b0e0b8..2564565f9e0623cdb2760a7e55e66f6d5aa612d6 100644 --- reth/crates/ethereum/consensus/src/validation.rs +++ scroll-reth/crates/ethereum/consensus/src/validation.rs @@ -64,7 +64,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/engine-primitives/src/payload.rs scroll-reth/crates/ethereum/engine-primitives/src/payload.rs index d43c43abc1bc278c46302f7dbe7fd067eda14041..b4f7127b5f0081ca672bfd55d0f94628b53262a5 100644 --- reth/crates/ethereum/engine-primitives/src/payload.rs +++ scroll-reth/crates/ethereum/engine-primitives/src/payload.rs @@ -64,6 +64,7 @@ self.fees }   /// Returns the blob sidecars. + #[allow(clippy::missing_const_for_fn)] pub fn sidecars(&self) -> &[BlobTransactionSidecar] { &self.sidecars }
diff --git reth/crates/optimism/node/src/engine.rs scroll-reth/crates/optimism/node/src/engine.rs index 6c089d9d5d86628f67f2cc21edabd3acb6c7ba6c..72e155eb80bf51615c0e8aca4a14872dd4f9c6fc 100644 --- reth/crates/optimism/node/src/engine.rs +++ scroll-reth/crates/optimism/node/src/engine.rs @@ -114,6 +114,7 @@ }   /// Returns the chain spec used by the validator. #[inline] + #[allow(clippy::missing_const_for_fn)] fn chain_spec(&self) -> &OpChainSpec { self.inner.chain_spec() }
diff --git reth/crates/optimism/payload/src/builder.rs scroll-reth/crates/optimism/payload/src/builder.rs index 8bf6396128069d152583a5cc22b4d6130223ba09..3e5af3cf0f47b9be055893f31da49eecb96fbda3 100644 --- reth/crates/optimism/payload/src/builder.rs +++ scroll-reth/crates/optimism/payload/src/builder.rs @@ -494,6 +494,7 @@ Evm: ConfigureEvm<Primitives: OpPayloadPrimitives, NextBlockEnvCtx = OpNextBlockEnvAttributes>, ChainSpec: EthChainSpec + OpHardforks, { /// 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 }
diff --git reth/crates/optimism/rpc/src/eth/mod.rs scroll-reth/crates/optimism/rpc/src/eth/mod.rs index 58278c8537358c698d8252c371b857dc20e450f1..40de13b74de5b5501596b0cedbf4621f6bbf8353 100644 --- reth/crates/optimism/rpc/src/eth/mod.rs +++ scroll-reth/crates/optimism/rpc/src/eth/mod.rs @@ -80,11 +80,13 @@ + 'static, >, { /// Returns a reference to the [`EthApiNodeBackend`]. + #[allow(clippy::missing_const_for_fn)] pub fn eth_api(&self) -> &EthApiNodeBackend<N> { self.inner.eth_api() }   /// Returns the configured sequencer client, if any. + #[allow(clippy::missing_const_for_fn)] pub fn sequencer_client(&self) -> Option<&SequencerClient> { self.inner.sequencer_client() }
diff --git reth/crates/optimism/rpc/src/sequencer.rs scroll-reth/crates/optimism/rpc/src/sequencer.rs index ae42f5d5b1869ba5f6bbf9659c91e1f38d19af14..3a359e74b050e3afb50031be29903173c78b9c9e 100644 --- reth/crates/optimism/rpc/src/sequencer.rs +++ scroll-reth/crates/optimism/rpc/src/sequencer.rs @@ -89,6 +89,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 14747109ebe8b4a4c54f688eb27f4094887b71c8..5080120ac0c082b99be25274e82e3aa973d9112c 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 }   # op op-alloy-consensus = { workspace = true, optional = true } @@ -95,6 +100,7 @@ "serde_json/std", "reth-chainspec/std", "revm-bytecode/std", "revm-state/std", + "scroll-alloy-consensus?/std", ] secp256k1 = ["dep:secp256k1"] test-utils = [ @@ -117,6 +123,7 @@ "secp256k1?/rand", "op-alloy-consensus?/arbitrary", "alloy-trie/arbitrary", "reth-chainspec/arbitrary", + "scroll-alloy-consensus?/arbitrary", ] serde-bincode-compat = [ "serde", @@ -125,6 +132,7 @@ "alloy-consensus/serde-bincode-compat", "alloy-eips/serde-bincode-compat", "op-alloy-consensus?/serde", "op-alloy-consensus?/serde-bincode-compat", + "scroll-alloy-consensus?/serde-bincode-compat", ] serde = [ "dep:serde", @@ -143,16 +151,19 @@ "alloy-trie/serde", "revm-bytecode/serde", "revm-state/serde", "rand_08/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/recovered.rs scroll-reth/crates/primitives-traits/src/block/recovered.rs index c565691dc646c4c0625dc64ebaa46b0b4e9e5e57..d4da8be16da0d7d6bf0649f66af7d19b4a2e8f54 100644 --- reth/crates/primitives-traits/src/block/recovered.rs +++ scroll-reth/crates/primitives-traits/src/block/recovered.rs @@ -58,6 +58,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/size.rs scroll-reth/crates/primitives-traits/src/size.rs index 0e3ad45aaa5281385a6708aef6b71505b4ee885c..a57aafd1ec962bab0f148059363c5caa60b28254 100644 --- reth/crates/primitives-traits/src/size.rs +++ scroll-reth/crates/primitives-traits/src/size.rs @@ -168,6 +168,34 @@ } } } } + +/// Implementations for scroll types. +#[cfg(feature = "scroll-alloy-traits")] +mod scroll { + use super::*; + + 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::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(), + } + } + } +} + #[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 1497ad2ac75c5262f51e68f6e6f0b92958617fec..7188bd3fe193c7b0a80a657c1e543f4fb2529e04 100644 --- reth/crates/primitives-traits/src/transaction/signed.rs +++ scroll-reth/crates/primitives-traits/src/transaction/signed.rs @@ -278,6 +278,36 @@ recover_signer_unchecked(signature, signature_hash) } } } + +#[cfg(feature = "scroll-alloy-traits")] +impl SignedTransaction for scroll_alloy_consensus::ScrollPooledTransaction { + fn tx_hash(&self) -> &TxHash { + match self { + Self::Legacy(tx) => tx.hash(), + Self::Eip2930(tx) => tx.hash(), + Self::Eip1559(tx) => tx.hash(), + } + } + + fn recover_signer(&self) -> Result<Address, RecoveryError> { + let signature_hash = self.signature_hash(); + recover_signer(self.signature(), signature_hash) + } + + fn recover_signer_unchecked_with_buf( + &self, + buf: &mut Vec<u8>, + ) -> Result<Address, RecoveryError> { + match self { + Self::Legacy(tx) => tx.tx().encode_for_signing(buf), + Self::Eip2930(tx) => tx.tx().encode_for_signing(buf), + Self::Eip1559(tx) => tx.tx().encode_for_signing(buf), + } + let signature_hash = keccak256(buf); + recover_signer_unchecked(self.signature(), signature_hash) + } +} + /// Opaque error type for sender recovery. #[derive(Debug, Default, thiserror::Error)] #[error("Failed to recover the signer")]
diff --git reth/crates/stages/stages/Cargo.toml scroll-reth/crates/stages/stages/Cargo.toml index ee74700b0a1a1755af90a674b3e079e0195ebb50..c8ef757320b883829bead17474888d26c8ce4bfd 100644 --- reth/crates/stages/stages/Cargo.toml +++ scroll-reth/crates/stages/stages/Cargo.toml @@ -116,6 +116,7 @@ "reth-prune-types/test-utils", "dep:reth-ethereum-primitives", "reth-ethereum-primitives?/test-utils", ] +skip-state-root-validation = []   [[bench]] name = "criterion"
diff --git reth/crates/stages/stages/benches/criterion.rs scroll-reth/crates/stages/stages/benches/criterion.rs index 08f700789ab4b91278ebc94b5068736b331cd2c8..c3905df2cc087ed5a00ef8be56a9d962eaa1b682 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,7 @@ group.sample_size(10);   let db = setup::txs_testdata(DEFAULT_NUM_BLOCKS);   - let stage = MerkleStage::Both { clean_threshold: u64::MAX }; + let stage = MerkleStage::<EthPrimitives>::Both { clean_threshold: u64::MAX }; measure_stage( runtime, &mut group, @@ -124,7 +125,7 @@ 1..=DEFAULT_NUM_BLOCKS, "Merkle-incremental".to_string(), );   - let stage = MerkleStage::Both { clean_threshold: 0 }; + let stage = MerkleStage::<EthPrimitives>::Both { clean_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 d602b1d247a8ef8a51e186db648bf2cafc21fb19..dff0d00d5c993ff13c5a1728f9d607be94c484b8 100644 --- reth/crates/stages/stages/src/sets.rs +++ scroll-reth/crates/stages/stages/src/sets.rs @@ -311,28 +311,35 @@ where E: BlockExecutorProvider, 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.executor_provider, self.consensus, 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(HistoryIndexingStages { - stages_config: self.stages_config.clone(), - prune_modes: self.prune_modes.clone(), - }) - // If any prune modes are set, add the prune stage. - .add_stage_opt(self.prune_modes.is_empty().not().then(|| { - // Prune stage should be added after all hashing stages, because otherwise it will - // delete - PruneStage::new(self.prune_modes.clone(), self.stages_config.prune.commit_threshold) - })) + ExecutionStages::new( + self.executor_provider, + 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(), + consensus: self.consensus, + }) + .add_set(HistoryIndexingStages { + stages_config: self.stages_config.clone(), + prune_modes: self.prune_modes.clone(), + }) + // If any prune modes are set, add the prune stage. + .add_stage_opt(self.prune_modes.is_empty().not().then(|| { + // Prune stage should be added after all hashing stages, because otherwise it will + // delete + PruneStage::new(self.prune_modes.clone(), self.stages_config.prune.commit_threshold) + })) } }   @@ -378,22 +385,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(), @@ -402,7 +412,10 @@ .add_stage(StorageHashingStage::new( self.stages_config.storage_hashing, self.stages_config.etl.clone(), )) - .add_stage(MerkleStage::new_execution(self.stages_config.merkle.clean_threshold)) + .add_stage(MerkleStage::new_execution( + self.stages_config.merkle.clean_threshold, + self.consensus, + )) } }
diff --git reth/crates/stages/stages/src/stages/merkle.rs scroll-reth/crates/stages/stages/src/stages/merkle.rs index 55173876e9643a473d586be484af4dcbe47ae247..ce31091763054066ea1cdeb30ccc25a49ac93851 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::BlockHeader; -use alloy_primitives::{BlockNumber, Sealable, B256}; +use alloy_consensus::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, UnwindInput, UnwindOutput, + EntitiesCheckpoint, ExecInput, ExecOutput, MerkleCheckpoint, Stage, StageCheckpoint, + StageError, StageId, 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 @@ -64,15 +67,23 @@ /// - [`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 { /// The threshold (in number of blocks) for switching from incremental trie building /// of changes to whole rebuild. clean_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 { @@ -82,20 +93,39 @@ clean_threshold: u64, }, }   -impl MerkleStage { - /// Stage default for the [`MerkleStage::Execution`]. - pub const fn default_execution() -> Self { - Self::Execution { clean_threshold: MERKLE_STAGE_DEFAULT_CLEAN_THRESHOLD } +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 { clean_threshold: MERKLE_STAGE_DEFAULT_CLEAN_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( + clean_threshold: u64, + consensus: Arc<dyn Consensus<P::Block, Error = ConsensusError>>, + ) -> Self { + Self::Execution { clean_threshold, consensus } }   - /// Create new instance of [`MerkleStage::Execution`]. - pub const fn new_execution(clean_threshold: u64) -> Self { - Self::Execution { clean_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 @@ -133,7 +163,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 @@ -141,12 +171,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"), } @@ -155,11 +186,11 @@ /// Execute the stage. fn execute(&mut self, provider: &Provider, input: ExecInput) -> Result<ExecOutput, StageError> { let 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 { clean_threshold } => *clean_threshold, + Self::Execution { clean_threshold, .. } => *clean_threshold, #[cfg(any(test, feature = "test-utils"))] Self::Both { clean_threshold } => *clean_threshold, }; @@ -279,7 +310,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) @@ -332,7 +369,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)?; @@ -347,21 +390,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)] @@ -371,10 +412,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; @@ -480,7 +522,7 @@ } }   impl StageTestRunner for MerkleTestRunner { - type S = MerkleStage; + type S = MerkleStage<EthPrimitives>;   fn db(&self) -> &TestStageDB { &self.db
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 a255703266a3c5e88a1ea8e6104b4b7cc9d12a53..0c7c7ab119e595def2e521ecba8c30b861cdb0e0 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 bf9f2354dd5ffbf11e9ea19f790da908b0c15170..4b02739f5be39d2bbdc53050f5db9a029616149a 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 fac3cd5084ce0f0419c591b0e57c5e12a8ff61ac..902208f1c73fb775fe110c090b47cfa7fe5de296 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 b4e39d709d8cb46b553addbfd3c57daa5ae901b3..bc7ef4587c87ae7c3797fd6a8948e9e37e962df9 100644 --- reth/crates/storage/nippy-jar/src/lib.rs +++ scroll-reth/crates/storage/nippy-jar/src/lib.rs @@ -412,6 +412,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 d32d9b514088bc495523072f268dc8576a8d7cfc..b20d9d65070aa334343df921c49d9ef4513d9cda 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/Cargo.toml scroll-reth/crates/storage/provider/Cargo.toml index 815ef09889543699efb08b0d57506c0790ee39b6..78c4e32bcf4111ddd4296f8b9d9c3203c59f5f8a 100644 --- reth/crates/storage/provider/Cargo.toml +++ scroll-reth/crates/storage/provider/Cargo.toml @@ -103,3 +103,4 @@ "reth-stages-types/test-utils", "revm-state", "tokio", ] +skip-state-root-validation = []
diff --git reth/crates/storage/provider/src/providers/database/provider.rs scroll-reth/crates/storage/provider/src/providers/database/provider.rs index dff8ececc9568a7a230baf7b3a9221b911d7edcb..c782bae75bf4d543dfc523b107e958b1a63227f4 100644 --- reth/crates/storage/provider/src/providers/database/provider.rs +++ scroll-reth/crates/storage/provider/src/providers/database/provider.rs @@ -277,7 +277,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 @@ -309,29 +309,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(()) @@ -525,6 +507,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/static_file/manager.rs scroll-reth/crates/storage/provider/src/providers/static_file/manager.rs index 9804697db8f98274c411a59797b2d9d7d5d19aa3..e0b0f75625b8768db1370d222710c2b8321f6dec 100644 --- reth/crates/storage/provider/src/providers/static_file/manager.rs +++ scroll-reth/crates/storage/provider/src/providers/static_file/manager.rs @@ -1102,6 +1102,7 @@ })) }   /// Returns directory where `static_files` are located. + #[allow(clippy::missing_const_for_fn)] pub fn directory(&self) -> &Path { &self.path } @@ -1193,12 +1194,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/trie/common/src/updates.rs scroll-reth/crates/trie/common/src/updates.rs index d4362542f005adb5dde7487ca94e25a7fbee95b8..e3146f50f908e4c48121113b12aa3d0a81bfb66d 100644 --- reth/crates/trie/common/src/updates.rs +++ scroll-reth/crates/trie/common/src/updates.rs @@ -363,6 +363,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 } @@ -396,6 +397,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..6867d422ba34fbf552d289cdeff7878a49d7b7e2 100644 --- reth/crates/trie/trie/Cargo.toml +++ scroll-reth/crates/trie/trie/Cargo.toml @@ -90,6 +90,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 1072d75dfaa5d18d2b93c942169a01a7a534f536..7ec2e506cf349dd928485971d3c56560e22bab16 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 c076bd693b3edf3648450a666a21f36f2e4800ad..c246ba1d1ba5553f2b260d3d6813ac43cbb1889e 100644 --- reth/Cargo.lock +++ scroll-reth/Cargo.lock @@ -874,7 +874,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e60987afbc49b8719578355726059579a1a197741a2adae52f860d5d7464cc" dependencies = [ "alloy-json-rpc", + "alloy-rpc-types-engine", "alloy-transport", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "jsonwebtoken", "reqwest", "serde_json", "tower 0.5.2", @@ -2436,6 +2442,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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" @@ -3764,6 +3780,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" @@ -4421,6 +4452,22 @@ "webpki-roots", ]   [[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.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5653,6 +5700,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" @@ -5993,12 +6057,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"   [[package]] +name = "openssl" +version = "0.10.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +dependencies = [ + "bitflags 2.9.0", + "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.101", +] + +[[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.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -7333,6 +7435,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", @@ -7607,6 +7710,7 @@ "reth-provider", "reth-prune", "reth-stages-api", "reth-transaction-pool", + "scroll-alloy-rpc-types-engine", "tokio", "tokio-stream", "tracing", @@ -8111,6 +8215,7 @@ "reth-storage-api", "reth-storage-errors", "reth-trie-common", "revm", + "scroll-alloy-evm", ]   [[package]] @@ -9228,6 +9333,7 @@ "reth-chain-state", "reth-chainspec", "reth-errors", "reth-primitives-traits", + "scroll-alloy-rpc-types-engine", "serde", "thiserror 2.0.12", "tokio", @@ -9303,6 +9409,7 @@ "reth-codecs", "revm-bytecode", "revm-primitives", "revm-state", + "scroll-alloy-consensus", "secp256k1", "serde", "serde_json", @@ -9816,6 +9923,320 @@ "serde", ]   [[package]] +name = "reth-scroll-chainspec" +version = "1.3.12" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips 0.15.6", + "alloy-genesis", + "alloy-primitives", + "alloy-serde 0.15.6", + "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.3.12" +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-evm", + "reth-scroll-node", + "reth-scroll-primitives", + "reth-tracing", + "scroll-alloy-consensus", + "tracing", +] + +[[package]] +name = "reth-scroll-consensus" +version = "1.3.12" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "reth-chainspec", + "reth-consensus", + "reth-consensus-common", + "reth-ethereum-consensus", + "reth-execution-types", + "reth-primitives", + "reth-primitives-traits", + "reth-scroll-primitives", + "scroll-alloy-hardforks", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "reth-scroll-engine-primitives" +version = "1.3.12" +dependencies = [ + "alloy-consensus", + "alloy-eips 0.15.6", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-engine", + "arbitrary", + "eyre", + "rand 0.9.1", + "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.8", +] + +[[package]] +name = "reth-scroll-evm" +version = "1.3.12" +dependencies = [ + "alloy-consensus", + "alloy-eips 0.15.6", + "alloy-evm", + "alloy-primitives", + "derive_more", + "eyre", + "reth-chainspec", + "reth-evm", + "reth-execution-types", + "reth-primitives", + "reth-primitives-traits", + "reth-scroll-chainspec", + "reth-scroll-forks", + "reth-scroll-primitives", + "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.3.12" +dependencies = [ + "alloy-chains", + "alloy-primitives", + "auto_impl", + "once_cell", + "reth-ethereum-forks", + "scroll-alloy-hardforks", + "serde", +] + +[[package]] +name = "reth-scroll-node" +version = "1.3.12" +dependencies = [ + "alloy-consensus", + "alloy-eips 0.15.6", + "alloy-genesis", + "alloy-network", + "alloy-primitives", + "alloy-rpc-types-engine", + "eyre", + "futures", + "reth-basic-payload-builder", + "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-rpc-types-engine", + "serde_json", + "thiserror 2.0.12", + "tokio", + "tracing", +] + +[[package]] +name = "reth-scroll-payload" +version = "1.3.12" +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-forks", + "reth-scroll-primitives", + "reth-storage-api", + "reth-transaction-pool", + "revm", + "scroll-alloy-consensus", + "scroll-alloy-hardforks", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "reth-scroll-primitives" +version = "1.3.12" +dependencies = [ + "alloy-consensus", + "alloy-eips 0.15.6", + "alloy-evm", + "alloy-primitives", + "alloy-rlp", + "arbitrary", + "bytes", + "derive_more", + "modular-bitfield", + "once_cell", + "proptest", + "proptest-arbitrary-interop", + "rand 0.8.5", + "rand 0.9.1", + "reth-codecs", + "reth-primitives-traits", + "reth-zstd-compressors", + "revm-context", + "revm-scroll", + "rstest", + "scroll-alloy-consensus", + "scroll-alloy-evm", + "secp256k1", + "serde", +] + +[[package]] +name = "reth-scroll-rpc" +version = "1.3.12" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rpc-types-eth", + "eyre", + "jsonrpsee-types", + "parking_lot", + "reth-chainspec", + "reth-evm", + "reth-network-api", + "reth-node-api", + "reth-node-builder", + "reth-primitives", + "reth-primitives-traits", + "reth-provider", + "reth-rpc", + "reth-rpc-eth-api", + "reth-rpc-eth-types", + "reth-scroll-chainspec", + "reth-scroll-primitives", + "reth-tasks", + "reth-transaction-pool", + "revm", + "scroll-alloy-consensus", + "scroll-alloy-evm", + "scroll-alloy-hardforks", + "scroll-alloy-network", + "scroll-alloy-rpc-types", + "thiserror 2.0.12", + "tokio", +] + +[[package]] +name = "reth-scroll-txpool" +version = "1.3.12" +dependencies = [ + "alloy-consensus", + "alloy-eips 0.15.6", + "alloy-primitives", + "alloy-rpc-types-eth", + "c-kzg", + "derive_more", + "futures-util", + "metrics", + "parking_lot", + "reth-chain-state", + "reth-chainspec", + "reth-metrics", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-scroll-chainspec", + "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.3.12" dependencies = [ @@ -10442,6 +10863,20 @@ "serde", ]   [[package]] +name = "revm-scroll" +version = "0.1.0" +source = "git+https://github.com/scroll-tech/scroll-revm#ccf29efd0762f6e45f7dc1c290668c643658080e" +dependencies = [ + "auto_impl", + "enumn", + "once_cell", + "revm", + "revm-inspector", + "revm-primitives", + "serde", +] + +[[package]] name = "revm-state" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -10717,7 +11152,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.2.0", ]   [[package]] @@ -10744,7 +11179,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4937d110d34408e9e5ad30ba0b0ca3b6a8a390f8db3636db60144ac4fa792750" dependencies = [ - "core-foundation", + "core-foundation 0.10.0", "core-foundation-sys", "jni", "log", @@ -10753,7 +11188,7 @@ "rustls", "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework", + "security-framework 3.2.0", "security-framework-sys", "webpki-root-certs", "windows-sys 0.59.0", @@ -10848,6 +11283,152 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"   [[package]] +name = "scroll-alloy-consensus" +version = "1.3.12" +dependencies = [ + "alloy-consensus", + "alloy-eips 0.15.6", + "alloy-primitives", + "alloy-rlp", + "alloy-serde 0.15.6", + "arbitrary", + "bincode 1.3.3", + "derive_more", + "modular-bitfield", + "proptest", + "proptest-arbitrary-interop", + "rand 0.9.1", + "reth-codecs", + "reth-codecs-derive", + "serde", + "serde_json", + "test-fuzz", +] + +[[package]] +name = "scroll-alloy-evm" +version = "1.3.12" +dependencies = [ + "alloy-consensus", + "alloy-eips 0.15.6", + "alloy-evm", + "alloy-primitives", + "auto_impl", + "eyre", + "revm", + "revm-scroll", + "scroll-alloy-consensus", + "scroll-alloy-hardforks", + "serde", +] + +[[package]] +name = "scroll-alloy-hardforks" +version = "1.3.12" +dependencies = [ + "alloy-hardforks", + "auto_impl", + "serde", +] + +[[package]] +name = "scroll-alloy-network" +version = "1.3.12" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-signer", + "scroll-alloy-consensus", + "scroll-alloy-rpc-types", +] + +[[package]] +name = "scroll-alloy-provider" +version = "1.3.12" +dependencies = [ + "alloy-json-rpc", + "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", + "jsonrpsee-types", + "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 0.4.13", +] + +[[package]] +name = "scroll-alloy-rpc-types" +version = "1.3.12" +dependencies = [ + "alloy-consensus", + "alloy-eips 0.15.6", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde 0.15.6", + "arbitrary", + "derive_more", + "scroll-alloy-consensus", + "serde", + "serde_json", + "similar-asserts", +] + +[[package]] +name = "scroll-alloy-rpc-types-engine" +version = "1.3.12" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-engine", + "arbitrary", + "serde", + "serde_json", +] + +[[package]] +name = "scroll-reth" +version = "1.3.12" +dependencies = [ + "clap", + "reth-cli-util", + "reth-engine-local", + "reth-provider", + "reth-scroll-cli", + "reth-scroll-node", + "tracing", +] + +[[package]] name = "sec1" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -10885,12 +11466,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.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags 2.9.0", - "core-foundation", + "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", @@ -11782,6 +12376,16 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.101", +] + +[[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]]
diff --git reth/Cargo.toml scroll-reth/Cargo.toml index bd9d9fec23c39ef10c51112e0004c18212704b41..21db9de1ada5713a2ec07d46d9c37db556573c76 100644 --- reth/Cargo.toml +++ scroll-reth/Cargo.toml @@ -106,6 +106,25 @@ "crates/rpc/rpc-server-types/", "crates/rpc/rpc-testing-util/", "crates/rpc/rpc-types-compat/", "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/", @@ -452,6 +471,7 @@ revm-context = { version = "3.0.0", default-features = false } revm-context-interface = { version = "3.0.0", default-features = false } revm-database-interface = { version = "3.0.0", default-features = false } op-revm = { version = "3.0.2", default-features = false } +revm-scroll = { git = "https://github.com/scroll-tech/scroll-revm", default-features = false } revm-inspectors = "0.20.0"   # eth @@ -495,6 +515,29 @@ alloy-transport-http = { version = "0.15.5", features = ["reqwest-rustls-tls"], default-features = false } alloy-transport-ipc = { version = "0.15.5", default-features = false } alloy-transport-ws = { version = "0.15.5", default-features = false }   +# scroll +scroll-alloy-consensus = { path = "crates/scroll/alloy/consensus", default-features = false } +scroll-alloy-evm = { path = "crates/scroll/alloy/evm" } +scroll-alloy-hardforks = { path = "crates/scroll/alloy/hardforks", default-features = false } +scroll-alloy-network = { path = "crates/scroll/alloy/network" } +scroll-alloy-rpc-types = { path = "crates/scroll/alloy/rpc-types" } +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" } +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" } +reth-scroll-rpc = { path = "crates/scroll/rpc" } +reth-scroll-trie = { path = "crates/scroll/trie" } +reth-scroll-txpool = { path = "crates/scroll/txpool" } +# TODO (scroll): point to crates.io/tag once the crate is published/a tag is created. +poseidon-bn254 = { git = "https://github.com/scroll-tech/poseidon-bn254", rev = "526a64a", features = ["bn254"] } + # op alloy-op-evm = { version = "0.6.0", default-features = false } alloy-op-hardforks = "0.2.0" @@ -507,7 +550,7 @@ op-alloy-flz = { version = "0.13.0", default-features = false }   # misc 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"
diff --git reth/Makefile scroll-reth/Makefile index 4ffc126b345e3ef80d6f29ef6533a5dff17d44cb..f99b7b888e9fbcaab066f570f39928fd25bffb7e 100644 --- reth/Makefile +++ scroll-reth/Makefile @@ -55,6 +55,13 @@ --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 \ + --features "skip-state-root-validation" \ + --profile "$(PROFILE)" \ + $(CARGO_INSTALL_EXTRA_FLAGS) + .PHONY: build build: ## Build the reth binary into `target` directory. cargo build --bin reth --features "$(FEATURES)" --profile "$(PROFILE)" @@ -391,6 +398,37 @@ --tests \ --benches \ --all-features \ -- -D warnings + +lint-scroll-reth: + cargo +nightly clippy \ + --workspace \ + --bin "scroll-reth" \ + --lib \ + --examples \ + --tests \ + --benches \ + --features "$(BIN_OTHER_FEATURES) skip-state-root-validation" \ + -- -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-codespell: ensure-codespell codespell --skip "*.json" --skip "./testing/ef-tests/ethereum-tests"
diff --git reth/crates/chainspec/Cargo.toml scroll-reth/crates/chainspec/Cargo.toml index 6d09d71c6344c90b80a380f0b922d5d78baaa596..9b06644072c5afdab20a51e379b602a665dc2b58 100644 --- reth/crates/chainspec/Cargo.toml +++ scroll-reth/crates/chainspec/Cargo.toml @@ -66,3 +66,4 @@ ] test-utils = [ "reth-primitives-traits/test-utils", ] +scroll = []
diff --git reth/crates/chainspec/src/spec.rs scroll-reth/crates/chainspec/src/spec.rs index 36487f8a4bb141db249f7fea7b1be26fd7503fb0..ec20e1c43b1436d7c108594ac2f2c9fd1fdf69c4 100644 --- reth/crates/chainspec/src/spec.rs +++ scroll-reth/crates/chainspec/src/spec.rs @@ -28,8 +28,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};   @@ -654,9 +654,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/stage/dump/merkle.rs scroll-reth/crates/cli/commands/src/stage/dump/merkle.rs index 744a4e81f480d41937139e621b93a45530ed8fa5..2c70d00b85e37280b4838f4bb181af3872a71592 100644 --- reth/crates/cli/commands/src/stage/dump/merkle.rs +++ scroll-reth/crates/cli/commands/src/stage/dump/merkle.rs @@ -92,7 +92,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( @@ -155,9 +155,10 @@ { 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 clean_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 a24a5eaa05f49b0a2d7445d6a3f317a25ff4be62..9ab9067a61587b693b010154d76bc6a523d5c2ca 100644 --- reth/crates/cli/commands/src/stage/run.rs +++ scroll-reth/crates/cli/commands/src/stage/run.rs @@ -298,10 +298,16 @@ etl_config, )), None, ), - StageEnum::Merkle => ( - Box::new(MerkleStage::new_execution(config.stages.merkle.clean_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.clean_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 7bf171d4797727cc4779d362871b2362d04def50..d9ddb70387a65c28a90bb303c00e84ca6ee05659 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/transaction.rs scroll-reth/crates/e2e-test-utils/src/transaction.rs index 1fc05fb5382011d792a2b24fd1341ba5d2593138..ca0cc4e77e534d135e6da8d80f23122a92790d74 100644 --- reth/crates/e2e-test-utils/src/transaction.rs +++ scroll-reth/crates/e2e-test-utils/src/transaction.rs @@ -26,6 +26,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); + 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 5d0eb22baca6e7706e98932e35f933792988691d..3709348816bc266b28ee40954c1b6efa4f466d76 100644 --- reth/crates/engine/local/Cargo.toml +++ scroll-reth/crates/engine/local/Cargo.toml @@ -30,6 +30,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 @@ -52,3 +55,8 @@ "dep:reth-optimism-chainspec", "reth-payload-primitives/op", "reth-evm/op", ] +scroll-alloy-traits = [ + "dep:scroll-alloy-rpc-types-engine", + "reth-evm/scroll-alloy-traits", + "reth-payload-primitives/scroll-alloy-traits", +]
diff --git reth/crates/engine/local/src/payload.rs scroll-reth/crates/engine/local/src/payload.rs index ea6c63083a94299a3138b602b787e1894c936975..af8d68bb7ce24484b3ece3c9bab350df1be74c95 100644 --- reth/crates/engine/local/src/payload.rs +++ scroll-reth/crates/engine/local/src/payload.rs @@ -64,6 +64,22 @@ } } }   +#[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: None, + } + } +} + /// A temporary workaround to support local payload engine launcher for arbitrary payload /// attributes. // TODO(mattsse): This should be reworked so that LocalPayloadAttributesBuilder can be implemented
diff --git reth/crates/engine/tree/Cargo.toml scroll-reth/crates/engine/tree/Cargo.toml index c51776ce39dddb90e3f0c58343a6b9803c6a72fd..0c26cc2271bd670c50788e3f7f14adb2825a3214 100644 --- reth/crates/engine/tree/Cargo.toml +++ scroll-reth/crates/engine/tree/Cargo.toml @@ -134,3 +134,4 @@ "reth-trie-parallel/test-utils", "reth-ethereum-primitives/test-utils", "reth-node-ethereum/test-utils", ] +skip-state-root-validation = ["reth-stages/skip-state-root-validation"]
diff --git reth/crates/engine/tree/benches/channel_perf.rs scroll-reth/crates/engine/tree/benches/channel_perf.rs index 74067d4de70e75ff4f7192d4ce481d465f2992da..448a25a05f1cadae4b7daccb71fc219c0a3e6a9b 100644 --- reth/crates/engine/tree/benches/channel_perf.rs +++ scroll-reth/crates/engine/tree/benches/channel_perf.rs @@ -25,7 +25,7 @@ info: AccountInfo { balance: U256::from(100), nonce: 10, code_hash: B256::from_slice(&rng.r#gen::<[u8; 32]>()), - code: Default::default(), + ..Default::default() }, storage, status: AccountStatus::Loaded,
diff --git reth/crates/engine/tree/benches/state_root_task.rs scroll-reth/crates/engine/tree/benches/state_root_task.rs index 1b6596f28d3b540a6550928b7a425adf5305eb92..2a762880be3304dccc398bbd0c879664f834942a 100644 --- reth/crates/engine/tree/benches/state_root_task.rs +++ scroll-reth/crates/engine/tree/benches/state_root_task.rs @@ -68,11 +68,13 @@ status: AccountStatus::SelfDestructed, } } else { RevmAccount { + #[allow(clippy::needless_update)] info: AccountInfo { balance: U256::from(rng.r#gen::<u64>()), nonce: rng.r#gen::<u64>(), code_hash: KECCAK_EMPTY, code: Some(Default::default()), + ..Default::default() }, storage: (0..rng.gen_range(0..=params.storage_slots_per_account)) .map(|_| {
diff --git reth/crates/engine/tree/src/tree/mod.rs scroll-reth/crates/engine/tree/src/tree/mod.rs index 56d69d337302e588a5f0fb95a3dbc112fc91bee0..5357172d0f29ded4c9dce97a4858b1f5a1d91f5c 100644 --- reth/crates/engine/tree/src/tree/mod.rs +++ scroll-reth/crates/engine/tree/src/tree/mod.rs @@ -23,7 +23,7 @@ use reth_chain_state::{ CanonicalInMemoryState, ExecutedBlock, ExecutedBlockWithTrieUpdates, MemoryOverlayStateProvider, NewCanonicalChain, }; -use reth_consensus::{Consensus, FullConsensus}; +use reth_consensus::{Consensus, FullConsensus, HeaderValidator}; pub use reth_engine_primitives::InvalidBlockHook; use reth_engine_primitives::{ BeaconConsensusEngineEvent, BeaconEngineMessage, BeaconOnNewPayloadError, EngineValidator, @@ -34,9 +34,7 @@ use reth_ethereum_primitives::EthPrimitives; use reth_evm::{execute::BlockExecutorProvider, ConfigureEvm}; use reth_payload_builder::PayloadBuilderHandle; use reth_payload_primitives::{EngineApiMessageVersion, PayloadBuilderAttributes, PayloadTypes}; -use reth_primitives_traits::{ - Block, GotExpected, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader, -}; +use reth_primitives_traits::{Block, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader}; use reth_provider::{ providers::ConsistentDbView, BlockNumReader, BlockReader, DBProvider, DatabaseProviderFactory, ExecutionOutcome, HashedPostStateProvider, ProviderError, StateCommitmentProvider, @@ -2548,17 +2546,17 @@ 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() { - // call post-block hook - self.on_invalid_block(&parent_block, &block, &output, Some((&trie_output, state_root))); - return Err(( - ConsensusError::BodyStateRootDiff( - GotExpected { got: state_root, expected: block.header().state_root() }.into(), - ) - .into(), - block, - )) - } + self.consensus + .validate_state_root(block.header(), state_root) + .inspect_err(|_| { + self.on_invalid_block( + &parent_block, + &block, + &output, + Some((&trie_output, state_root)), + ); + }) + .map_err(|err| (err.into(), block.clone()))?;   // terminate prewarming task with good state output handle.terminate_caching(Some(output.state.clone()));
diff --git reth/crates/ethereum-forks/src/display.rs scroll-reth/crates/ethereum-forks/src/display.rs index eb6538dd688fced05e9c3f1a771789130bfae55d..7921761d255cbd9b3899be2f8f004129c912b2cb 100644 --- reth/crates/ethereum-forks/src/display.rs +++ scroll-reth/crates/ethereum-forks/src/display.rs @@ -118,7 +118,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/evm/evm/Cargo.toml scroll-reth/crates/evm/evm/Cargo.toml index 6095df3a51f6fedec54fbf659e69dab208927470..09852419efb20f0e7b4b041edf8d157ebcbeaf17 100644 --- reth/crates/evm/evm/Cargo.toml +++ scroll-reth/crates/evm/evm/Cargo.toml @@ -31,6 +31,9 @@ alloy-eips.workspace = true alloy-evm.workspace = true alloy-consensus.workspace = true   +# scroll +scroll-alloy-evm = { workspace = true, optional = true } + auto_impl.workspace = true derive_more.workspace = true futures-util.workspace = true @@ -63,6 +66,7 @@ "futures-util/std", "derive_more/std", "reth-storage-api/std", "reth-trie-common/std", + "scroll-alloy-evm?/std", ] metrics = ["std", "dep:metrics", "dep:reth-metrics"] test-utils = [ @@ -72,3 +76,4 @@ "reth-primitives-traits/test-utils", "reth-trie-common/test-utils", ] op = ["op-revm", "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/execute.rs scroll-reth/crates/evm/evm/src/execute.rs index 8e9d0f6dda444e0c18269bc348c92940b501815c..4a4740f01909bd7f9b274f59fe757ef8327c7428 100644 --- reth/crates/evm/evm/src/execute.rs +++ scroll-reth/crates/evm/evm/src/execute.rs @@ -525,7 +525,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; @@ -600,12 +599,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 } @@ -641,8 +636,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(); @@ -663,8 +657,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 72b1ef70c57b6f465f76f7d98c88d68cd6af0cae..7589b337d08b9da940404794496064206de1dc14 100644 --- reth/crates/evm/evm/src/lib.rs +++ scroll-reth/crates/evm/evm/src/lib.rs @@ -363,3 +363,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 d5bc8a0ee23e10dba3fc1a180a130871b09028d5..e54343aa91498d3d6f4445797cd6ce85a8e59565 100644 --- reth/crates/evm/evm/src/metrics.rs +++ scroll-reth/crates/evm/evm/src/metrics.rs @@ -138,6 +138,7 @@ }   #[cfg(test)] mod tests { + #![allow(clippy::needless_update)] use super::*; use alloy_eips::eip7685::Requests; use alloy_primitives::{B256, U256}; @@ -239,6 +240,7 @@ balance: U256::from(100), nonce: 10, code_hash: B256::random(), code: Default::default(), + ..Default::default() }, storage, status: AccountStatus::Loaded,
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/src/network.rs scroll-reth/crates/net/network/src/network.rs index a45eecda05ab447c6b8e1cb424733364ef304ee6..c1822887d90c100ef2a6c1688bbaf11cafdf1226 100644 --- reth/crates/net/network/src/network.rs +++ scroll-reth/crates/net/network/src/network.rs @@ -88,15 +88,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 +185,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 }
diff --git reth/crates/net/network/src/test_utils/testnet.rs scroll-reth/crates/net/network/src/test_utils/testnet.rs index b29341a94055873f6a6ccf1795abbfbdf76b5a6e..e2c80249c08b3bb281ac962b9d2faca4165f5fae 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 } @@ -348,6 +349,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/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/Cargo.toml scroll-reth/crates/node/builder/Cargo.toml index d08c62d38ceeaf9da4db4460c784772ea28f22e7..b8103d8b3dce3b35d3948c43ce06c2c87f40b135 100644 --- reth/crates/node/builder/Cargo.toml +++ scroll-reth/crates/node/builder/Cargo.toml @@ -111,3 +111,7 @@ "reth-db-api/op", "reth-engine-local/op", "reth-evm/op", ] +skip-state-root-validation = [ + "reth-stages/skip-state-root-validation", + "reth-engine-tree/skip-state-root-validation", +]
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 ee503c90005152f2932d04c3135df46fd6e437d6..6a1a69a05ac5710fe35f5a5dafae17f5e5744a58 100644 --- reth/crates/payload/primitives/src/traits.rs +++ scroll-reth/crates/payload/primitives/src/traits.rs @@ -121,6 +121,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 + } +} + /// A builder that can return the current payload attribute. pub trait PayloadAttributesBuilder<Attributes>: Send + Sync + 'static { /// Return a new payload attribute from the builder.
diff --git reth/crates/rpc/rpc-eth-types/src/fee_history.rs scroll-reth/crates/rpc/rpc-eth-types/src/fee_history.rs index da67e92dcd35185d8b56b829fc6be5e11c2eea68..fd5313c8c0cb45cafc9210ae2c3aedbbfa54b87f 100644 --- reth/crates/rpc/rpc-eth-types/src/fee_history.rs +++ scroll-reth/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -47,6 +47,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/src/debug.rs scroll-reth/crates/rpc/rpc/src/debug.rs index 7521082b84b0fee1da27692dd314eea2d08a526f..af8a699d127679e16486bd7f4e34169bedb6ad3b 100644 --- reth/crates/rpc/rpc/src/debug.rs +++ scroll-reth/crates/rpc/rpc/src/debug.rs @@ -68,6 +68,7 @@ Self { inner } }   /// 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/bundle.rs scroll-reth/crates/rpc/rpc/src/eth/bundle.rs index 07ed06fdc90903c830341335afe76e0499280790..1c67139582e252102cdbdb1067d799ea5deb194f 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 95e0e2daaf1289f5b85810130a0f6c15b4cb882b..dc8233fe77af1006659f4779c37cb0c48116106b 100644 --- reth/crates/rpc/rpc/src/eth/filter.rs +++ scroll-reth/crates/rpc/rpc/src/eth/filter.rs @@ -133,6 +133,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 0bd3fb67076b1368776c57057e9fe59c047f9458..9bd78ad851e35e657fa25cd1499b61203f568692 100644 --- reth/crates/rpc/rpc/src/eth/sim_bundle.rs +++ scroll-reth/crates/rpc/rpc/src/eth/sim_bundle.rs @@ -74,6 +74,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 a032db1084d31864d7459901cfa006e476bba056..b868d72d3a261e9c024caac2d8907ebaad83439a 100644 --- reth/crates/rpc/rpc/src/reth.rs +++ scroll-reth/crates/rpc/rpc/src/reth.rs @@ -22,6 +22,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 ecdca8a12b7e1dbb89f1bd54630ec54177a630b8..989330a1b9c4265d0815c55945e4a1882c8528b6 100644 --- reth/crates/rpc/rpc/src/trace.rs +++ scroll-reth/crates/rpc/rpc/src/trace.rs @@ -61,6 +61,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/transaction-pool/Cargo.toml scroll-reth/crates/transaction-pool/Cargo.toml index 4b24a347e5e7b57ed2e558b8e5a2a59faf4ca54b..d232607bb228f862a9c867af7ba138e66e395966 100644 --- reth/crates/transaction-pool/Cargo.toml +++ scroll-reth/crates/transaction-pool/Cargo.toml @@ -59,6 +59,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 0dab27f4677d60d87cd94ccf09be4ad4f988463f..39689e7ae6f47cdcbe061b99cbbf82108fe95756 100644 --- reth/crates/transaction-pool/src/lib.rs +++ scroll-reth/crates/transaction-pool/src/lib.rs @@ -230,6 +230,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/pool/mod.rs scroll-reth/crates/transaction-pool/src/pool/mod.rs index aee6badb5d4861803df690f13b1314d56530ba16..c5a78db59367cc0fb804ffa91f4c401be86eb263 100644 --- reth/crates/transaction-pool/src/pool/mod.rs +++ scroll-reth/crates/transaction-pool/src/pool/mod.rs @@ -997,6 +997,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 } @@ -1087,6 +1088,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), @@ -1130,6 +1132,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 57fb45eaa51002e9de73ff93d78e5d93d85c6c0d..39ea8fa870aac3dbdd9d4a8e229ea79adb626bee 100644 --- reth/crates/transaction-pool/src/pool/pending.rs +++ scroll-reth/crates/transaction-pool/src/pool/pending.rs @@ -571,6 +571,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 36953f454ebacc687e6aec2f0c9df363e256e6c4..d4b1ec80795d1719c84370fcaf8d5094d827c1b1 100644 --- reth/crates/transaction-pool/src/validate/eth.rs +++ scroll-reth/crates/transaction-pool/src/validate/eth.rs @@ -57,6 +57,7 @@ self.client().chain_spec() }   /// Returns the configured client + #[allow(clippy::missing_const_for_fn)] pub fn client(&self) -> &Client { &self.inner.client } @@ -188,6 +189,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() }
diff --git reth/deny.toml scroll-reth/deny.toml index 8372af6fe6c151b298057b99d92d73fac702fcdb..958943571b20ae13e6e911a3b30170f7b214ec6c 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 @@ -64,6 +64,21 @@ { allow = ["MPL-2.0"], name = "webpki-roots" }, { 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", +] + +[[licenses.clarify]] +name = "ring" +expression = "LicenseRef-ring" +license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] + [[licenses.clarify]] name = "rustls-webpki" expression = "LicenseRef-rustls-webpki" @@ -87,4 +102,8 @@ "https://github.com/bluealloy/revm", "https://github.com/paradigmxyz/revm-inspectors", "https://github.com/alloy-rs/evm", "https://github.com/alloy-rs/hardforks", + "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", ]
diff --git reth/.github/assets/check_rv32imac.sh scroll-reth/.github/assets/check_rv32imac.sh index 3338e9786269adc2d28b0d4ec14e25e003a40ab0..2df538757977e78250fa55e4b00abeabd019dbff 100755 --- reth/.github/assets/check_rv32imac.sh +++ scroll-reth/.github/assets/check_rv32imac.sh @@ -35,6 +35,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 2eced75399bd422a700dd8e993344c72207f7b06..4fc5f1fe2299e946a95b142714c76a5cdbb849f2 100755 --- reth/.github/assets/check_wasm.sh +++ scroll-reth/.github/assets/check_wasm.sh @@ -73,6 +73,18 @@ reth-testing-utils reth-optimism-txpool # reth-transaction-pool reth-era-downloader # tokio reth-era-import # tokio + 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 0215bf304c11f6f71869f7805ee31236fd518440..04cafe6d60576878b7cb4efa72e6d799b296de53 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 @@ -19,8 +19,7 @@ name: bench jobs: codspeed: - runs-on: - group: Reth + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: @@ -40,4 +39,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 837d47e9f84c5f3844ec6f0eb48fa16d696b7249..ac6650624c3adbde641d8ef32f326cf2676bf959 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] merge_group:   jobs:
diff --git reth/.github/workflows/compact.yml scroll-reth/.github/workflows/compact.yml index 06ddec20cb7c3b730b05f37ed6c7ef7ffa26ec4a..f87f97fa25433875dbc1fb5ca62f56e12348d3d7 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 "skip-state-root-validation 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/hive.yml scroll-reth/.github/workflows/hive.yml index 095facc72409ea5b80902022d0c78374c3c6c2f2..892c0cac70938c7e62201a3ae47ad01c201afec3 100644 --- reth/.github/workflows/hive.yml +++ scroll-reth/.github/workflows/hive.yml @@ -25,8 +25,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 aad87b0fea84d875b0e9daec53cb25fcf4a93e60..3e9a8139dc7c45b8d0e4650d177c66a439bfe04c 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: @@ -66,7 +61,7 @@ uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }}   - era-files: + era-files: name: era1 file integration tests once a day if: github.event_name == 'schedule' runs-on: ubuntu-latest
diff --git reth/.github/workflows/lint.yml scroll-reth/.github/workflows/lint.yml index 099866a02f068c94546f8da2dffad5c6e22bc70a..1febd6363c6f3bf8d8011c3f3191127fea39294d 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: "skip-state-root-validation asm-keccak jemalloc jemalloc-prof min-error-logs min-warn-logs min-info-logs min-debug-logs min-trace-logs" - type: book args: --manifest-path book/sources/Cargo.toml --workspace --bins features: "" @@ -106,6 +109,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 @@ -174,7 +178,6 @@ with: cache-on-failure: true - uses: taiki-e/install-action@cargo-udeps - run: cargo udeps --workspace --lib --examples --tests --benches --all-features --locked - book: name: book runs-on: ubuntu-latest @@ -238,7 +241,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
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/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.yml scroll-reth/.github/workflows/sync.yml index 952cab361abaf40431e4ed6c754f82612cdbae8e..98fa0a0f20e89a0bddaa4aa65a017b64d6e2275c 100644 --- reth/.github/workflows/sync.yml +++ scroll-reth/.github/workflows/sync.yml @@ -4,6 +4,8 @@ name: sync test   on: merge_group: + push: + branches: [main, scroll]   env: CARGO_TERM_COLOR: always @@ -15,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 @@ -36,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
diff --git reth/.github/workflows/unit.yml scroll-reth/.github/workflows/unit.yml index 767a3e5c0adf4b722ee6cac7620b8f4213dc3978..50f86a938a1396feabe67462750cf67c5a8fa402 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,12 +42,18 @@ - 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 - type: book args: --manifest-path book/sources/Cargo.toml 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 @@ -64,18 +70,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)"   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 @@ -95,8 +102,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..d8a7bbf1913a5a2befb763bbdea6fcda4574ca78 --- /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: 8c2277b2d5b3bfe43e4b8e06d995905269a67b74 +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/**"