Skip to main content

sof_tx/submit/
types.rs

1//! Shared submission types, errors, and transport traits.
2
3use std::time::Duration;
4
5use async_trait::async_trait;
6use solana_signature::Signature;
7use thiserror::Error;
8
9use crate::{builder::BuilderError, providers::LeaderTarget, routing::RoutingPolicy};
10
11/// Runtime submit mode.
12#[derive(Debug, Clone, Copy, Eq, PartialEq)]
13pub enum SubmitMode {
14    /// Submit only through JSON-RPC.
15    RpcOnly,
16    /// Submit only through direct leader/validator targets.
17    DirectOnly,
18    /// Submit direct first, then RPC fallback on failure.
19    Hybrid,
20}
21
22/// Reliability profile for direct and hybrid submission behavior.
23#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
24pub enum SubmitReliability {
25    /// Fastest path with minimal retrying.
26    LowLatency,
27    /// Balanced latency and retry behavior.
28    #[default]
29    Balanced,
30    /// Aggressive retrying before giving up.
31    HighReliability,
32}
33
34/// Signed transaction payload variants accepted by submit APIs.
35#[derive(Debug, Clone, Eq, PartialEq)]
36pub enum SignedTx {
37    /// Bincode-serialized `VersionedTransaction` bytes.
38    VersionedTransactionBytes(Vec<u8>),
39    /// Wire-format transaction bytes.
40    WireTransactionBytes(Vec<u8>),
41}
42
43/// RPC submit tuning.
44#[derive(Debug, Clone, Eq, PartialEq)]
45pub struct RpcSubmitConfig {
46    /// Skip preflight simulation when true.
47    pub skip_preflight: bool,
48    /// Optional preflight commitment string.
49    pub preflight_commitment: Option<String>,
50}
51
52impl Default for RpcSubmitConfig {
53    fn default() -> Self {
54        Self {
55            skip_preflight: true,
56            preflight_commitment: None,
57        }
58    }
59}
60
61/// Direct submit tuning.
62#[derive(Debug, Clone, Eq, PartialEq)]
63pub struct DirectSubmitConfig {
64    /// Per-target send timeout.
65    pub per_target_timeout: Duration,
66    /// Global send budget for one submission.
67    pub global_timeout: Duration,
68    /// Number of rounds to iterate across selected direct targets.
69    pub direct_target_rounds: usize,
70    /// Number of direct submit attempts in `Hybrid` mode before RPC fallback.
71    pub hybrid_direct_attempts: usize,
72}
73
74impl DirectSubmitConfig {
75    /// Builds a direct-submit config from a reliability profile.
76    #[must_use]
77    pub const fn from_reliability(reliability: SubmitReliability) -> Self {
78        match reliability {
79            SubmitReliability::LowLatency => Self {
80                per_target_timeout: Duration::from_millis(200),
81                global_timeout: Duration::from_millis(700),
82                direct_target_rounds: 1,
83                hybrid_direct_attempts: 1,
84            },
85            SubmitReliability::Balanced => Self {
86                per_target_timeout: Duration::from_millis(300),
87                global_timeout: Duration::from_millis(1_200),
88                direct_target_rounds: 2,
89                hybrid_direct_attempts: 2,
90            },
91            SubmitReliability::HighReliability => Self {
92                per_target_timeout: Duration::from_millis(450),
93                global_timeout: Duration::from_millis(2_200),
94                direct_target_rounds: 3,
95                hybrid_direct_attempts: 3,
96            },
97        }
98    }
99
100    /// Returns this config with minimum valid retry counters.
101    #[must_use]
102    pub const fn normalized(self) -> Self {
103        let direct_target_rounds = if self.direct_target_rounds == 0 {
104            1
105        } else {
106            self.direct_target_rounds
107        };
108        let hybrid_direct_attempts = if self.hybrid_direct_attempts == 0 {
109            1
110        } else {
111            self.hybrid_direct_attempts
112        };
113        Self {
114            per_target_timeout: self.per_target_timeout,
115            global_timeout: self.global_timeout,
116            direct_target_rounds,
117            hybrid_direct_attempts,
118        }
119    }
120}
121
122impl Default for DirectSubmitConfig {
123    fn default() -> Self {
124        Self::from_reliability(SubmitReliability::default())
125    }
126}
127
128/// Low-level transport errors surfaced by submit backends.
129#[derive(Debug, Error, Clone, Eq, PartialEq)]
130pub enum SubmitTransportError {
131    /// Invalid transport configuration.
132    #[error("transport configuration invalid: {message}")]
133    Config {
134        /// Human-readable description.
135        message: String,
136    },
137    /// Transport operation failed.
138    #[error("transport failure: {message}")]
139    Failure {
140        /// Human-readable description.
141        message: String,
142    },
143}
144
145/// Submission-level errors.
146#[derive(Debug, Error)]
147pub enum SubmitError {
148    /// Could not build/sign transaction for builder submit path.
149    #[error("failed to build/sign transaction: {source}")]
150    Build {
151        /// Builder-layer failure.
152        source: BuilderError,
153    },
154    /// No blockhash available for builder submit path.
155    #[error("blockhash provider returned no recent blockhash")]
156    MissingRecentBlockhash,
157    /// Signed bytes could not be decoded into a transaction.
158    #[error("failed to decode signed transaction bytes: {source}")]
159    DecodeSignedBytes {
160        /// Bincode decode error.
161        source: Box<bincode::ErrorKind>,
162    },
163    /// Duplicate signature was suppressed by dedupe window.
164    #[error("duplicate signature suppressed by dedupe window")]
165    DuplicateSignature,
166    /// RPC mode requested but no RPC transport was configured.
167    #[error("rpc transport is not configured")]
168    MissingRpcTransport,
169    /// Direct mode requested but no direct transport was configured.
170    #[error("direct transport is not configured")]
171    MissingDirectTransport,
172    /// No direct targets resolved from routing inputs.
173    #[error("no direct targets resolved from leader/backups")]
174    NoDirectTargets,
175    /// Direct transport failure.
176    #[error("direct submit failed: {source}")]
177    Direct {
178        /// Direct transport error.
179        source: SubmitTransportError,
180    },
181    /// RPC transport failure.
182    #[error("rpc submit failed: {source}")]
183    Rpc {
184        /// RPC transport error.
185        source: SubmitTransportError,
186    },
187    /// Internal synchronization failure.
188    #[error("internal synchronization failure: {message}")]
189    InternalSync {
190        /// Synchronization error details.
191        message: String,
192    },
193}
194
195/// Summary of a successful submission.
196#[derive(Debug, Clone, Eq, PartialEq)]
197pub struct SubmitResult {
198    /// Signature parsed from submitted transaction bytes.
199    pub signature: Option<Signature>,
200    /// Mode selected by caller.
201    pub mode: SubmitMode,
202    /// Target chosen by direct path when applicable.
203    pub direct_target: Option<LeaderTarget>,
204    /// RPC-returned signature string when RPC path succeeded.
205    pub rpc_signature: Option<String>,
206    /// True when RPC fallback was used from hybrid mode.
207    pub used_rpc_fallback: bool,
208}
209
210/// RPC transport interface.
211#[async_trait]
212pub trait RpcSubmitTransport: Send + Sync {
213    /// Submits transaction bytes to RPC and returns signature string.
214    async fn submit_rpc(
215        &self,
216        tx_bytes: &[u8],
217        config: &RpcSubmitConfig,
218    ) -> Result<String, SubmitTransportError>;
219}
220
221/// Direct transport interface.
222#[async_trait]
223pub trait DirectSubmitTransport: Send + Sync {
224    /// Submits transaction bytes to direct targets and returns the first successful target.
225    async fn submit_direct(
226        &self,
227        tx_bytes: &[u8],
228        targets: &[LeaderTarget],
229        policy: RoutingPolicy,
230        config: &DirectSubmitConfig,
231    ) -> Result<LeaderTarget, SubmitTransportError>;
232}