Skip to content

feat: support directly forward transactions to sequencer #265

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: scroll
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bin/reth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ tokio = { workspace = true, features = ["sync", "macros", "time", "rt-multi-thre
aquamarine.workspace = true
clap = { workspace = true, features = ["derive", "env"] }
eyre.workspace = true
alloy-transport.workspace = true

[dev-dependencies]
backon.workspace = true
Expand Down
10 changes: 10 additions & 0 deletions crates/scroll/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ alloy-primitives.workspace = true
alloy-rpc-types-eth.workspace = true
alloy-consensus.workspace = true
revm.workspace = true
alloy-transport.workspace = true
alloy-json-rpc.workspace = true
alloy-rpc-client.workspace = true
alloy-transport-http.workspace = true

# reqwest
reqwest = { workspace = true, default-features = false, features = ["rustls-tls-native-roots"] }

# tracing
tracing.workspace = true

# async
parking_lot.workspace = true
Expand Down
36 changes: 36 additions & 0 deletions crates/scroll/rpc/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! RPC errors specific to Scroll.

use alloy_json_rpc::ErrorPayload;
use alloy_rpc_types_eth::BlockError;
use alloy_transport::{RpcError, TransportErrorKind};
use jsonrpsee_types::error::{INTERNAL_ERROR_CODE};
use reth_evm::execute::ProviderError;
use reth_rpc_eth_api::{AsEthApiError, TransactionConversionError};
use reth_rpc_eth_types::{error::api::FromEvmHalt, EthApiError};
Expand All @@ -12,12 +15,16 @@ pub enum ScrollEthApiError {
/// L1 ethereum error.
#[error(transparent)]
Eth(#[from] EthApiError),
/// Sequencer client error.
#[error(transparent)]
Sequencer(#[from] SequencerClientError),
}

impl AsEthApiError for ScrollEthApiError {
fn as_err(&self) -> Option<&EthApiError> {
match self {
Self::Eth(err) => Some(err),
_ => None,
}
}
}
Expand All @@ -26,6 +33,7 @@ impl From<ScrollEthApiError> for jsonrpsee_types::error::ErrorObject<'static> {
fn from(err: ScrollEthApiError) -> Self {
match err {
ScrollEthApiError::Eth(err) => err.into(),
ScrollEthApiError::Sequencer(err) => err.into(),
}
}
}
Expand Down Expand Up @@ -62,3 +70,31 @@ impl From<ProviderError> for ScrollEthApiError {
Self::Eth(EthApiError::from(value))
}
}

/// Error type when interacting with the Sequencer
#[derive(Debug, thiserror::Error)]
pub enum SequencerClientError {
/// Wrapper around an [`RpcError<TransportErrorKind>`].
#[error(transparent)]
HttpError(#[from] RpcError<TransportErrorKind>),
/// Thrown when serializing transaction to forward to sequencer
#[error("invalid sequencer transaction")]
InvalidSequencerTransaction,
}

impl From<SequencerClientError> for jsonrpsee_types::error::ErrorObject<'static> {
fn from(err: SequencerClientError) -> Self {
match err {
SequencerClientError::HttpError(RpcError::ErrorResp(ErrorPayload {
code,
message,
data,
})) => jsonrpsee_types::error::ErrorObject::owned(code as i32, message, data),
err => jsonrpsee_types::error::ErrorObject::owned(
INTERNAL_ERROR_CODE,
err.to_string(),
None::<String>,
),
}
}
}
47 changes: 42 additions & 5 deletions crates/scroll/rpc/src/eth/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Scroll-Reth `eth_` endpoint implementation.

use alloy_primitives::U256;
use eyre::WrapErr;
use reth_chainspec::{EthChainSpec, EthereumHardforks};
use reth_evm::ConfigureEvm;
use reth_network_api::NetworkInfo;
Expand Down Expand Up @@ -40,6 +41,8 @@ mod pending_block;
pub mod receipt;
pub mod transaction;

use crate::SequencerClient;

/// Adapter for [`EthApiInner`], which holds all the data required to serve core `eth_` API.
pub type EthApiNodeBackend<N> = EthApiInner<
<N as RpcNodeCore>::Provider,
Expand Down Expand Up @@ -74,8 +77,8 @@ pub struct ScrollEthApi<N: ScrollNodeCore, NetworkT = Scroll> {

impl<N: ScrollNodeCore, NetworkT> ScrollEthApi<N, NetworkT> {
/// Creates a new [`ScrollEthApi`].
pub fn new(eth_api: EthApiNodeBackend<N>) -> Self {
let inner = Arc::new(ScrollEthApiInner { eth_api });
pub fn new(eth_api: EthApiNodeBackend<N>, sequencer_client: Option<SequencerClient>) -> Self {
let inner = Arc::new(ScrollEthApiInner { eth_api, sequencer_client });
Self {
inner: inner.clone(),
_nt: PhantomData,
Expand All @@ -99,6 +102,11 @@ where
self.inner.eth_api()
}

/// Returns the configured sequencer client, if any.
pub fn sequencer_client(&self) -> Option<&SequencerClient> {
self.inner.sequencer_client()
}

/// Return a builder for the [`ScrollEthApi`].
pub const fn builder() -> ScrollEthApiBuilder {
ScrollEthApiBuilder::new()
Expand Down Expand Up @@ -302,23 +310,41 @@ impl<N: ScrollNodeCore, NetworkT> fmt::Debug for ScrollEthApi<N, NetworkT> {
pub struct ScrollEthApiInner<N: ScrollNodeCore> {
/// Gateway to node's core components.
pub eth_api: EthApiNodeBackend<N>,
/// Sequencer client, configured to forward submitted transactions to sequencer of given Scroll
/// network.
sequencer_client: Option<SequencerClient>,
}

impl<N: ScrollNodeCore> ScrollEthApiInner<N> {
/// Returns a reference to the [`EthApiNodeBackend`].
const fn eth_api(&self) -> &EthApiNodeBackend<N> {
&self.eth_api
}

/// Returns the configured sequencer client, if any.
const fn sequencer_client(&self) -> Option<&SequencerClient> {
self.sequencer_client.as_ref()
}
}

/// A type that knows how to build a [`ScrollEthApi`].
#[derive(Debug, Default)]
pub struct ScrollEthApiBuilder {}
pub struct ScrollEthApiBuilder {
/// Sequencer client, configured to forward submitted transactions to sequencer of given Scroll
/// network.
sequencer_url: Option<String>,
}

impl ScrollEthApiBuilder {
/// Creates a [`ScrollEthApiBuilder`] instance.
pub const fn new() -> Self {
Self {}
Self { sequencer_url: None }
}

/// With a [`SequencerClient`].
pub fn with_sequencer(mut self, sequencer_url: Option<String>) -> Self {
self.sequencer_url = sequencer_url;
self
}
}

Expand All @@ -330,6 +356,7 @@ where
type EthApi = ScrollEthApi<N>;

async fn build_eth_api(self, ctx: EthApiCtx<'_, N>) -> eyre::Result<Self::EthApi> {
let Self { sequencer_url } = self;
let eth_api = reth_rpc::EthApiBuilder::new(
ctx.components.provider().clone(),
ctx.components.pool().clone(),
Expand All @@ -345,6 +372,16 @@ where
.proof_permits(ctx.config.proof_permits)
.build_inner();

Ok(ScrollEthApi::new(eth_api))
let sequencer_client = if let Some(url) = sequencer_url {
Some(
SequencerClient::new(&url)
.await
.wrap_err_with(|| "Failed to init sequencer client with: {url}")?,
)
} else {
None
};

Ok(ScrollEthApi::new(eth_api, sequencer_client))
}
}
46 changes: 43 additions & 3 deletions crates/scroll/rpc/src/eth/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use crate::{
eth::{ScrollEthApiInner, ScrollNodeCore},
ScrollEthApi,
ScrollEthApi, ScrollEthApiError, SequencerClient,
};
use alloy_consensus::transaction::TransactionInfo;
use alloy_primitives::{Bytes, B256};
Expand All @@ -13,7 +13,7 @@ use reth_provider::{
};
use reth_rpc_eth_api::{
helpers::{EthSigner, EthTransactions, LoadTransaction, SpawnBlocking},
try_into_scroll_tx_info, FromEthApiError, FullEthApiTypes, RpcNodeCore, RpcNodeCoreExt,
try_into_scroll_tx_info, EthApiTypes, FromEthApiError, FullEthApiTypes, RpcNodeCore, RpcNodeCoreExt,
TxInfoMapper,
};
use reth_rpc_eth_types::utils::recover_raw_transaction;
Expand All @@ -27,7 +27,7 @@ use std::{

impl<N> EthTransactions for ScrollEthApi<N>
where
Self: LoadTransaction<Provider: BlockReaderIdExt>,
Self: LoadTransaction<Provider: BlockReaderIdExt> + EthApiTypes<Error = ScrollEthApiError>,
N: ScrollNodeCore<Provider: BlockReader<Transaction = ProviderTx<Self::Provider>>>,
{
fn signers(&self) -> &parking_lot::RwLock<Vec<Box<dyn EthSigner<ProviderTx<Self::Provider>>>>> {
Expand All @@ -41,6 +41,36 @@ where
let recovered = recover_raw_transaction(&tx)?;
let pool_transaction = <Self::Pool as TransactionPool>::Transaction::from_pooled(recovered);

// On scroll, transactions are forwarded directly to the sequencer to be included in
// blocks that it builds.
if let Some(client) = self.raw_tx_forwarder().as_ref() {
tracing::debug!(target: "rpc::eth", hash = %pool_transaction.hash(), "forwarding raw transaction to sequencer");

match client.forward_raw_transaction(&tx).await {
Ok(hash) => {
// Sequencer succeeded, try to add to local pool too
let _ = self
.pool()
.add_transaction(TransactionOrigin::Local, pool_transaction)
.await.inspect_err(|err| {
tracing::debug!(target: "rpc::eth", %err, %hash, "successfully sent tx to sequencer, but failed to persist in local tx pool");
});
return Ok(hash);
}
Err(err) => {
tracing::warn!(target: "rpc::eth", %err, hash=% *pool_transaction.hash(), "failed to forward raw transaction to sequencer");
// Sequencer failed, try local pool instead
let hash = self
.pool()
.add_transaction(TransactionOrigin::Local, pool_transaction)
.await
.map_err(Self::Error::from_eth_err)?;
tracing::debug!(target: "rpc::eth", %hash, "failed to forward tx to sequencer, but successfully added to local tx pool");
return Ok(hash);
}
}
}

// submit the transaction to the pool with a `Local` origin
let hash = self
.pool()
Expand All @@ -60,6 +90,16 @@ where
{
}

impl<N> ScrollEthApi<N>
where
N: ScrollNodeCore,
{
/// Returns the [`SequencerClient`] if one is set.
pub fn raw_tx_forwarder(&self) -> Option<SequencerClient> {
self.inner.sequencer_client.clone()
}
}

/// Scroll implementation of [`TxInfoMapper`].
///
/// Receipt is fetched to extract the `l1_fee` for all transactions but L1 messages.
Expand Down
4 changes: 3 additions & 1 deletion crates/scroll/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

pub mod error;
pub mod eth;
pub mod sequencer;

pub use error::ScrollEthApiError;
pub use error::{ScrollEthApiError, SequencerClientError};
pub use eth::{ScrollEthApi, ScrollReceiptBuilder};
pub use sequencer::SequencerClient;
Loading
Loading