reth_optimism_txpool/supervisor/
client.rs
1use crate::{
4 supervisor::{
5 parse_access_list_items_to_inbox_entries, ExecutingDescriptor, InteropTxValidatorError,
6 },
7 InvalidCrossTx,
8};
9use alloy_eips::eip2930::AccessList;
10use alloy_primitives::{TxHash, B256};
11use alloy_rpc_client::ReqwestClient;
12use futures_util::future::BoxFuture;
13use op_alloy_consensus::interop::SafetyLevel;
14use std::{borrow::Cow, future::IntoFuture, time::Duration};
15use tracing::trace;
16
17pub const DEFAULT_SUPERVISOR_URL: &str = "http://localhost:1337/";
20
21const DEFAULT_REQUEST_TIMOUT: Duration = Duration::from_millis(100);
23
24#[derive(Debug, Clone)]
26pub struct SupervisorClient {
27 client: ReqwestClient,
28 safety: SafetyLevel,
30 timeout: Duration,
32}
33
34impl SupervisorClient {
35 pub async fn new(supervisor_endpoint: impl Into<String>, safety: SafetyLevel) -> Self {
37 let client = ReqwestClient::builder()
38 .connect(supervisor_endpoint.into().as_str())
39 .await
40 .expect("building supervisor client");
41 Self { client, safety, timeout: DEFAULT_REQUEST_TIMOUT }
42 }
43
44 pub fn with_timeout(mut self, timeout: Duration) -> Self {
46 self.timeout = timeout;
47 self
48 }
49
50 pub fn safety(&self) -> SafetyLevel {
52 self.safety
53 }
54
55 pub fn check_access_list<'a>(
57 &self,
58 inbox_entries: &'a [B256],
59 executing_descriptor: ExecutingDescriptor,
60 ) -> CheckAccessListRequest<'a> {
61 CheckAccessListRequest {
62 client: self.client.clone(),
63 inbox_entries: Cow::Borrowed(inbox_entries),
64 executing_descriptor,
65 timeout: self.timeout,
66 safety: self.safety,
67 }
68 }
69
70 pub async fn is_valid_cross_tx(
80 &self,
81 access_list: Option<&AccessList>,
82 hash: &TxHash,
83 timestamp: u64,
84 timeout: Option<u64>,
85 is_interop_active: bool,
86 ) -> Option<Result<(), InvalidCrossTx>> {
87 let access_list = access_list?;
90 let inbox_entries = parse_access_list_items_to_inbox_entries(access_list.iter())
91 .copied()
92 .collect::<Vec<_>>();
93 if inbox_entries.is_empty() {
94 return None;
95 }
96
97 if !is_interop_active {
99 return Some(Err(InvalidCrossTx::CrossChainTxPreInterop))
101 }
102
103 if let Err(err) = self
104 .check_access_list(
105 inbox_entries.as_slice(),
106 ExecutingDescriptor::new(timestamp, timeout),
107 )
108 .await
109 {
110 trace!(target: "txpool", hash=%hash, err=%err, "Cross chain transaction invalid");
111 return Some(Err(InvalidCrossTx::ValidationError(err)));
112 }
113 Some(Ok(()))
114 }
115}
116
117#[derive(Debug, Clone)]
119pub struct CheckAccessListRequest<'a> {
120 client: ReqwestClient,
121 inbox_entries: Cow<'a, [B256]>,
122 executing_descriptor: ExecutingDescriptor,
123 timeout: Duration,
124 safety: SafetyLevel,
125}
126
127impl CheckAccessListRequest<'_> {
128 pub fn with_timeout(mut self, timeout: Duration) -> Self {
130 self.timeout = timeout;
131 self
132 }
133
134 pub fn with_safety(mut self, safety: SafetyLevel) -> Self {
136 self.safety = safety;
137 self
138 }
139}
140
141impl<'a> IntoFuture for CheckAccessListRequest<'a> {
142 type Output = Result<(), InteropTxValidatorError>;
143 type IntoFuture = BoxFuture<'a, Self::Output>;
144
145 fn into_future(self) -> Self::IntoFuture {
146 let Self { client, inbox_entries, executing_descriptor, timeout, safety } = self;
147 Box::pin(async move {
148 tokio::time::timeout(
149 timeout,
150 client.request(
151 "supervisor_checkAccessList",
152 (inbox_entries, safety, executing_descriptor),
153 ),
154 )
155 .await
156 .map_err(|_| InteropTxValidatorError::ValidationTimeout(timeout.as_secs()))?
157 .map_err(InteropTxValidatorError::client)
158 })
159 }
160}