Skip to content

Commit 18bff3a

Browse files
yiweichigreged93
andauthored
feat: support directly forward transactions to sequencer (#265)
* feat: support forward transaction to sequencer * remove unused code * fix: transaction already know * update logic to keep it same as l2geth * fix: make clippy happy Signed-off-by: Gregory Edison <[email protected]> * fix: ci This reverts commit 27bd9da. * fix: ci not found HeaderProvider * fix: ci not found try_into_scroll_tx_info * fix: ci fmt * fix: ci clippy * feat: enable override pool propagate_local_transactions * fix: EthTransactionValidatorBuilder accepts custom local_transactions_config * fix: remove propagate_local_transactions * fix: ci * address comments * fix: logging --------- Signed-off-by: Gregory Edison <[email protected]> Co-authored-by: Gregory Edison <[email protected]>
1 parent d250986 commit 18bff3a

File tree

10 files changed

+362
-25
lines changed

10 files changed

+362
-25
lines changed

Cargo.lock

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/rpc/rpc-eth-api/src/helpers/trace.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,11 +327,13 @@ pub trait Trace:
327327

328328
// prepare transactions, we do everything upfront to reduce time spent with open
329329
// state
330-
let max_transactions =
331-
highest_index.map_or(block.body().transaction_count(), |highest| {
330+
let max_transactions = highest_index.map_or_else(
331+
|| block.body().transaction_count(),
332+
|highest| {
332333
// we need + 1 because the index is 0-based
333334
highest as usize + 1
334-
});
335+
},
336+
);
335337

336338
let mut idx = 0;
337339

crates/scroll/node/src/builder/pool.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ where
6565
.no_eip4844()
6666
.with_head_timestamp(ctx.head().timestamp)
6767
.kzg_settings(ctx.kzg_settings()?)
68+
.with_local_transactions_config(
69+
pool_config_overrides.clone().apply(ctx.pool_config()).local_transactions_config,
70+
)
6871
.with_additional_tasks(
6972
pool_config_overrides
7073
.additional_validation_tasks

crates/scroll/rpc/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@ alloy-primitives.workspace = true
4343
alloy-rpc-types-eth.workspace = true
4444
alloy-consensus.workspace = true
4545
revm.workspace = true
46+
alloy-transport.workspace = true
47+
alloy-json-rpc.workspace = true
48+
alloy-rpc-client.workspace = true
49+
alloy-transport-http.workspace = true
50+
51+
# reqwest
52+
reqwest = { workspace = true, default-features = false, features = ["rustls-tls-native-roots"] }
53+
54+
# tracing
55+
tracing.workspace = true
4656

4757
# async
4858
parking_lot.workspace = true

crates/scroll/rpc/src/error.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
//! RPC errors specific to Scroll.
22
3+
use alloy_json_rpc::ErrorPayload;
34
use alloy_rpc_types_eth::BlockError;
5+
use alloy_transport::{RpcError, TransportErrorKind};
6+
use jsonrpsee_types::error::INTERNAL_ERROR_CODE;
47
use reth_evm::execute::ProviderError;
58
use reth_rpc_convert::transaction::EthTxEnvError;
69
use reth_rpc_eth_api::{AsEthApiError, TransactionConversionError};
@@ -13,12 +16,16 @@ pub enum ScrollEthApiError {
1316
/// L1 ethereum error.
1417
#[error(transparent)]
1518
Eth(#[from] EthApiError),
19+
/// Sequencer client error.
20+
#[error(transparent)]
21+
Sequencer(#[from] SequencerClientError),
1622
}
1723

1824
impl AsEthApiError for ScrollEthApiError {
1925
fn as_err(&self) -> Option<&EthApiError> {
2026
match self {
2127
Self::Eth(err) => Some(err),
28+
_ => None,
2229
}
2330
}
2431
}
@@ -27,6 +34,7 @@ impl From<ScrollEthApiError> for jsonrpsee_types::error::ErrorObject<'static> {
2734
fn from(err: ScrollEthApiError) -> Self {
2835
match err {
2936
ScrollEthApiError::Eth(err) => err.into(),
37+
ScrollEthApiError::Sequencer(err) => err.into(),
3038
}
3139
}
3240
}
@@ -69,3 +77,31 @@ impl From<ProviderError> for ScrollEthApiError {
6977
Self::Eth(EthApiError::from(value))
7078
}
7179
}
80+
81+
/// Error type when interacting with the Sequencer
82+
#[derive(Debug, thiserror::Error)]
83+
pub enum SequencerClientError {
84+
/// Wrapper around an [`RpcError<TransportErrorKind>`].
85+
#[error(transparent)]
86+
HttpError(#[from] RpcError<TransportErrorKind>),
87+
/// Thrown when serializing transaction to forward to sequencer
88+
#[error("invalid sequencer transaction")]
89+
InvalidSequencerTransaction,
90+
}
91+
92+
impl From<SequencerClientError> for jsonrpsee_types::error::ErrorObject<'static> {
93+
fn from(err: SequencerClientError) -> Self {
94+
match err {
95+
SequencerClientError::HttpError(RpcError::ErrorResp(ErrorPayload {
96+
code,
97+
message,
98+
data,
99+
})) => jsonrpsee_types::error::ErrorObject::owned(code as i32, message, data),
100+
err => jsonrpsee_types::error::ErrorObject::owned(
101+
INTERNAL_ERROR_CODE,
102+
err.to_string(),
103+
None::<String>,
104+
),
105+
}
106+
}
107+
}

crates/scroll/rpc/src/eth/mod.rs

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Scroll-Reth `eth_` endpoint implementation.
22
33
use alloy_primitives::U256;
4+
use eyre::WrapErr;
45
use reth_chainspec::{EthChainSpec, EthereumHardforks};
56
use reth_evm::ConfigureEvm;
67
use reth_network_api::NetworkInfo;
@@ -40,6 +41,8 @@ mod pending_block;
4041
pub mod receipt;
4142
pub mod transaction;
4243

44+
use crate::SequencerClient;
45+
4346
/// Adapter for [`EthApiInner`], which holds all the data required to serve core `eth_` API.
4447
pub type EthApiNodeBackend<N> = EthApiInner<
4548
<N as RpcNodeCore>::Provider,
@@ -73,8 +76,8 @@ pub struct ScrollEthApi<N: ScrollNodeCore, NetworkT = Scroll> {
7376

7477
impl<N: ScrollNodeCore, NetworkT> ScrollEthApi<N, NetworkT> {
7578
/// Creates a new [`ScrollEthApi`].
76-
pub fn new(eth_api: EthApiNodeBackend<N>) -> Self {
77-
let inner = Arc::new(ScrollEthApiInner { eth_api });
79+
pub fn new(eth_api: EthApiNodeBackend<N>, sequencer_client: Option<SequencerClient>) -> Self {
80+
let inner = Arc::new(ScrollEthApiInner { eth_api, sequencer_client });
7881
Self {
7982
inner: inner.clone(),
8083
_nt: PhantomData,
@@ -98,6 +101,11 @@ where
98101
self.inner.eth_api()
99102
}
100103

104+
/// Returns the configured sequencer client, if any.
105+
pub fn sequencer_client(&self) -> Option<&SequencerClient> {
106+
self.inner.sequencer_client()
107+
}
108+
101109
/// Return a builder for the [`ScrollEthApi`].
102110
pub const fn builder() -> ScrollEthApiBuilder {
103111
ScrollEthApiBuilder::new()
@@ -307,23 +315,41 @@ impl<N: ScrollNodeCore, NetworkT> fmt::Debug for ScrollEthApi<N, NetworkT> {
307315
pub struct ScrollEthApiInner<N: ScrollNodeCore> {
308316
/// Gateway to node's core components.
309317
pub eth_api: EthApiNodeBackend<N>,
318+
/// Sequencer client, configured to forward submitted transactions to sequencer of given Scroll
319+
/// network.
320+
sequencer_client: Option<SequencerClient>,
310321
}
311322

312323
impl<N: ScrollNodeCore> ScrollEthApiInner<N> {
313324
/// Returns a reference to the [`EthApiNodeBackend`].
314325
const fn eth_api(&self) -> &EthApiNodeBackend<N> {
315326
&self.eth_api
316327
}
328+
329+
/// Returns the configured sequencer client, if any.
330+
const fn sequencer_client(&self) -> Option<&SequencerClient> {
331+
self.sequencer_client.as_ref()
332+
}
317333
}
318334

319335
/// A type that knows how to build a [`ScrollEthApi`].
320336
#[derive(Debug, Default)]
321-
pub struct ScrollEthApiBuilder {}
337+
pub struct ScrollEthApiBuilder {
338+
/// Sequencer client, configured to forward submitted transactions to sequencer of given Scroll
339+
/// network.
340+
sequencer_url: Option<String>,
341+
}
322342

323343
impl ScrollEthApiBuilder {
324344
/// Creates a [`ScrollEthApiBuilder`] instance.
325345
pub const fn new() -> Self {
326-
Self {}
346+
Self { sequencer_url: None }
347+
}
348+
349+
/// With a [`SequencerClient`].
350+
pub fn with_sequencer(mut self, sequencer_url: Option<String>) -> Self {
351+
self.sequencer_url = sequencer_url;
352+
self
327353
}
328354
}
329355

@@ -335,6 +361,7 @@ where
335361
type EthApi = ScrollEthApi<N>;
336362

337363
async fn build_eth_api(self, ctx: EthApiCtx<'_, N>) -> eyre::Result<Self::EthApi> {
364+
let Self { sequencer_url } = self;
338365
let eth_api = reth_rpc::EthApiBuilder::new(
339366
ctx.components.provider().clone(),
340367
ctx.components.pool().clone(),
@@ -350,6 +377,16 @@ where
350377
.proof_permits(ctx.config.proof_permits)
351378
.build_inner();
352379

353-
Ok(ScrollEthApi::new(eth_api))
380+
let sequencer_client = if let Some(url) = sequencer_url {
381+
Some(
382+
SequencerClient::new(&url)
383+
.await
384+
.wrap_err_with(|| "Failed to init sequencer client with: {url}")?,
385+
)
386+
} else {
387+
None
388+
};
389+
390+
Ok(ScrollEthApi::new(eth_api, sequencer_client))
354391
}
355392
}

crates/scroll/rpc/src/eth/transaction.rs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::{
44
eth::{ScrollEthApiInner, ScrollNodeCore},
5-
ScrollEthApi,
5+
ScrollEthApi, ScrollEthApiError, SequencerClient,
66
};
77
use alloy_consensus::transaction::TransactionInfo;
88
use alloy_primitives::{Bytes, B256};
@@ -11,10 +11,10 @@ use reth_node_api::FullNodeComponents;
1111
use reth_provider::{
1212
BlockReader, BlockReaderIdExt, ProviderTx, ReceiptProvider, TransactionsProvider,
1313
};
14-
use reth_rpc_convert::try_into_scroll_tx_info;
1514
use reth_rpc_eth_api::{
1615
helpers::{EthSigner, EthTransactions, LoadTransaction, SpawnBlocking},
17-
FromEthApiError, FullEthApiTypes, RpcNodeCore, RpcNodeCoreExt, TxInfoMapper,
16+
try_into_scroll_tx_info, EthApiTypes, FromEthApiError, FullEthApiTypes, RpcNodeCore,
17+
RpcNodeCoreExt, TxInfoMapper,
1818
};
1919
use reth_rpc_eth_types::utils::recover_raw_transaction;
2020
use reth_scroll_primitives::ScrollReceipt;
@@ -27,7 +27,7 @@ use std::{
2727

2828
impl<N> EthTransactions for ScrollEthApi<N>
2929
where
30-
Self: LoadTransaction<Provider: BlockReaderIdExt>,
30+
Self: LoadTransaction<Provider: BlockReaderIdExt> + EthApiTypes<Error = ScrollEthApiError>,
3131
N: ScrollNodeCore<Provider: BlockReader<Transaction = ProviderTx<Self::Provider>>>,
3232
{
3333
fn signers(&self) -> &parking_lot::RwLock<Vec<Box<dyn EthSigner<ProviderTx<Self::Provider>>>>> {
@@ -41,6 +41,33 @@ where
4141
let recovered = recover_raw_transaction(&tx)?;
4242
let pool_transaction = <Self::Pool as TransactionPool>::Transaction::from_pooled(recovered);
4343

44+
// On scroll, transactions are forwarded directly to the sequencer to be included in
45+
// blocks that it builds.
46+
if let Some(client) = self.raw_tx_forwarder().as_ref() {
47+
tracing::debug!(target: "scroll::rpc::eth", hash = %pool_transaction.hash(), "forwarding raw transaction to sequencer");
48+
49+
// Retain tx in local tx pool before forwarding to sequencer rpc, for local RPC usage.
50+
let hash = self
51+
.pool()
52+
.add_transaction(TransactionOrigin::Local, pool_transaction.clone())
53+
.await
54+
.map_err(Self::Error::from_eth_err)?;
55+
56+
tracing::debug!(target: "scroll::rpc::eth", %hash, "successfully added transaction to local tx pool");
57+
58+
// Forward to remote sequencer RPC.
59+
match client.forward_raw_transaction(&tx).await {
60+
Ok(sequencer_hash) => {
61+
tracing::debug!(target: "scroll::rpc::eth", local_hash=%hash, sequencer_hash=%sequencer_hash, "successfully forwarded transaction to sequencer");
62+
}
63+
Err(err) => {
64+
tracing::warn!(target: "scroll::rpc::eth", %err, %hash, "failed to forward transaction to sequencer, but transaction is in local pool");
65+
}
66+
}
67+
68+
return Ok(hash);
69+
}
70+
4471
// submit the transaction to the pool with a `Local` origin
4572
let hash = self
4673
.pool()
@@ -60,6 +87,16 @@ where
6087
{
6188
}
6289

90+
impl<N> ScrollEthApi<N>
91+
where
92+
N: ScrollNodeCore,
93+
{
94+
/// Returns the [`SequencerClient`] if one is set.
95+
pub fn raw_tx_forwarder(&self) -> Option<SequencerClient> {
96+
self.inner.sequencer_client.clone()
97+
}
98+
}
99+
63100
/// Scroll implementation of [`TxInfoMapper`].
64101
///
65102
/// Receipt is fetched to extract the `l1_fee` for all transactions but L1 messages.

crates/scroll/rpc/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
pub mod error;
1212
pub mod eth;
13+
pub mod sequencer;
1314

14-
pub use error::ScrollEthApiError;
15+
pub use error::{ScrollEthApiError, SequencerClientError};
1516
pub use eth::{ScrollEthApi, ScrollReceiptBuilder};
17+
pub use sequencer::SequencerClient;

0 commit comments

Comments
 (0)