reth_optimism_txpool/supervisor/
client.rs

1//! This is our custom implementation of validator struct
2
3use 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
17/// Supervisor hosted by op-labs
18// TODO: This should be changes to actual supervisor url
19pub const DEFAULT_SUPERVISOR_URL: &str = "http://localhost:1337/";
20
21/// The default request timeout to use
22const DEFAULT_REQUEST_TIMOUT: Duration = Duration::from_millis(100);
23
24/// Implementation of the supervisor trait for the interop.
25#[derive(Debug, Clone)]
26pub struct SupervisorClient {
27    client: ReqwestClient,
28    /// The default
29    safety: SafetyLevel,
30    /// The default request timeout
31    timeout: Duration,
32}
33
34impl SupervisorClient {
35    /// Creates a new supervisor validator.
36    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    /// Configures a custom timeout
45    pub fn with_timeout(mut self, timeout: Duration) -> Self {
46        self.timeout = timeout;
47        self
48    }
49
50    /// Returns safely level
51    pub fn safety(&self) -> SafetyLevel {
52        self.safety
53    }
54
55    /// Executes a `supervisor_checkAccessList` with the configured safety level.
56    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    /// Extracts commitment from access list entries, pointing to 0x420..022 and validates them
71    /// against supervisor.
72    ///
73    /// If commitment present pre-interop tx rejected.
74    ///
75    /// Returns:
76    /// None - if tx is not cross chain,
77    /// Some(Ok(()) - if tx is valid cross chain,
78    /// Some(Err(e)) - if tx is not valid or interop is not active
79    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        // We don't need to check for deposit transaction in here, because they won't come from
88        // txpool
89        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        // Interop check
98        if !is_interop_active {
99            // No cross chain tx allowed before interop
100            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/// A Request future that issues a `supervisor_checkAccessList` request.
118#[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    /// Configures the timeout to use for the request if any.
129    pub fn with_timeout(mut self, timeout: Duration) -> Self {
130        self.timeout = timeout;
131        self
132    }
133
134    /// Configures the [`SafetyLevel`] for this request
135    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}