1use 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#[derive(Debug, Clone, Copy, Eq, PartialEq)]
13pub enum SubmitMode {
14 RpcOnly,
16 DirectOnly,
18 Hybrid,
20}
21
22#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
24pub enum SubmitReliability {
25 LowLatency,
27 #[default]
29 Balanced,
30 HighReliability,
32}
33
34#[derive(Debug, Clone, Eq, PartialEq)]
36pub enum SignedTx {
37 VersionedTransactionBytes(Vec<u8>),
39 WireTransactionBytes(Vec<u8>),
41}
42
43#[derive(Debug, Clone, Eq, PartialEq)]
45pub struct RpcSubmitConfig {
46 pub skip_preflight: bool,
48 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#[derive(Debug, Clone, Eq, PartialEq)]
63pub struct DirectSubmitConfig {
64 pub per_target_timeout: Duration,
66 pub global_timeout: Duration,
68 pub direct_target_rounds: usize,
70 pub direct_submit_attempts: usize,
72 pub hybrid_direct_attempts: usize,
74 pub rebroadcast_interval: Duration,
76 pub agave_rebroadcast_enabled: bool,
78 pub agave_rebroadcast_window: Duration,
80 pub agave_rebroadcast_interval: Duration,
82 pub hybrid_rpc_broadcast: bool,
84 pub latency_aware_targeting: bool,
86 pub latency_probe_timeout: Duration,
88 pub latency_probe_port: Option<u16>,
90 pub latency_probe_max_targets: usize,
92}
93
94impl DirectSubmitConfig {
95 #[must_use]
97 pub const fn from_reliability(reliability: SubmitReliability) -> Self {
98 match reliability {
99 SubmitReliability::LowLatency => Self {
100 per_target_timeout: Duration::from_millis(200),
101 global_timeout: Duration::from_millis(1_200),
102 direct_target_rounds: 3,
103 direct_submit_attempts: 3,
104 hybrid_direct_attempts: 2,
105 rebroadcast_interval: Duration::from_millis(90),
106 agave_rebroadcast_enabled: true,
107 agave_rebroadcast_window: Duration::from_secs(30),
108 agave_rebroadcast_interval: Duration::from_millis(700),
109 hybrid_rpc_broadcast: false,
110 latency_aware_targeting: true,
111 latency_probe_timeout: Duration::from_millis(80),
112 latency_probe_port: Some(8899),
113 latency_probe_max_targets: 128,
114 },
115 SubmitReliability::Balanced => Self {
116 per_target_timeout: Duration::from_millis(300),
117 global_timeout: Duration::from_millis(1_800),
118 direct_target_rounds: 4,
119 direct_submit_attempts: 4,
120 hybrid_direct_attempts: 3,
121 rebroadcast_interval: Duration::from_millis(110),
122 agave_rebroadcast_enabled: true,
123 agave_rebroadcast_window: Duration::from_secs(45),
124 agave_rebroadcast_interval: Duration::from_millis(800),
125 hybrid_rpc_broadcast: true,
126 latency_aware_targeting: true,
127 latency_probe_timeout: Duration::from_millis(120),
128 latency_probe_port: Some(8899),
129 latency_probe_max_targets: 128,
130 },
131 SubmitReliability::HighReliability => Self {
132 per_target_timeout: Duration::from_millis(450),
133 global_timeout: Duration::from_millis(3_200),
134 direct_target_rounds: 6,
135 direct_submit_attempts: 5,
136 hybrid_direct_attempts: 4,
137 rebroadcast_interval: Duration::from_millis(140),
138 agave_rebroadcast_enabled: true,
139 agave_rebroadcast_window: Duration::from_secs(70),
140 agave_rebroadcast_interval: Duration::from_millis(900),
141 hybrid_rpc_broadcast: true,
142 latency_aware_targeting: true,
143 latency_probe_timeout: Duration::from_millis(160),
144 latency_probe_port: Some(8899),
145 latency_probe_max_targets: 128,
146 },
147 }
148 }
149
150 #[must_use]
152 pub const fn normalized(self) -> Self {
153 let direct_target_rounds = if self.direct_target_rounds == 0 {
154 1
155 } else {
156 self.direct_target_rounds
157 };
158 let direct_submit_attempts = if self.direct_submit_attempts == 0 {
159 1
160 } else {
161 self.direct_submit_attempts
162 };
163 let hybrid_direct_attempts = if self.hybrid_direct_attempts == 0 {
164 1
165 } else {
166 self.hybrid_direct_attempts
167 };
168 let latency_probe_max_targets = if self.latency_probe_max_targets == 0 {
169 1
170 } else {
171 self.latency_probe_max_targets
172 };
173 let rebroadcast_interval = if self.rebroadcast_interval.is_zero() {
174 Duration::from_millis(1)
175 } else {
176 self.rebroadcast_interval
177 };
178 let agave_rebroadcast_interval = if self.agave_rebroadcast_interval.is_zero() {
179 Duration::from_millis(1)
180 } else {
181 self.agave_rebroadcast_interval
182 };
183 Self {
184 per_target_timeout: self.per_target_timeout,
185 global_timeout: self.global_timeout,
186 direct_target_rounds,
187 direct_submit_attempts,
188 hybrid_direct_attempts,
189 rebroadcast_interval,
190 agave_rebroadcast_enabled: self.agave_rebroadcast_enabled,
191 agave_rebroadcast_window: self.agave_rebroadcast_window,
192 agave_rebroadcast_interval,
193 hybrid_rpc_broadcast: self.hybrid_rpc_broadcast,
194 latency_aware_targeting: self.latency_aware_targeting,
195 latency_probe_timeout: self.latency_probe_timeout,
196 latency_probe_port: self.latency_probe_port,
197 latency_probe_max_targets,
198 }
199 }
200}
201
202impl Default for DirectSubmitConfig {
203 fn default() -> Self {
204 Self::from_reliability(SubmitReliability::default())
205 }
206}
207
208#[derive(Debug, Error, Clone, Eq, PartialEq)]
210pub enum SubmitTransportError {
211 #[error("transport configuration invalid: {message}")]
213 Config {
214 message: String,
216 },
217 #[error("transport failure: {message}")]
219 Failure {
220 message: String,
222 },
223}
224
225#[derive(Debug, Error)]
227pub enum SubmitError {
228 #[error("failed to build/sign transaction: {source}")]
230 Build {
231 source: BuilderError,
233 },
234 #[error("blockhash provider returned no recent blockhash")]
236 MissingRecentBlockhash,
237 #[error("failed to decode signed transaction bytes: {source}")]
239 DecodeSignedBytes {
240 source: Box<bincode::ErrorKind>,
242 },
243 #[error("duplicate signature suppressed by dedupe window")]
245 DuplicateSignature,
246 #[error("rpc transport is not configured")]
248 MissingRpcTransport,
249 #[error("direct transport is not configured")]
251 MissingDirectTransport,
252 #[error("no direct targets resolved from leader/backups")]
254 NoDirectTargets,
255 #[error("direct submit failed: {source}")]
257 Direct {
258 source: SubmitTransportError,
260 },
261 #[error("rpc submit failed: {source}")]
263 Rpc {
264 source: SubmitTransportError,
266 },
267 #[error("internal synchronization failure: {message}")]
269 InternalSync {
270 message: String,
272 },
273}
274
275#[derive(Debug, Clone, Eq, PartialEq)]
277pub struct SubmitResult {
278 pub signature: Option<Signature>,
280 pub mode: SubmitMode,
282 pub direct_target: Option<LeaderTarget>,
284 pub rpc_signature: Option<String>,
286 pub used_rpc_fallback: bool,
288 pub selected_target_count: usize,
290 pub selected_identity_count: usize,
292}
293
294#[async_trait]
296pub trait RpcSubmitTransport: Send + Sync {
297 async fn submit_rpc(
299 &self,
300 tx_bytes: &[u8],
301 config: &RpcSubmitConfig,
302 ) -> Result<String, SubmitTransportError>;
303}
304
305#[async_trait]
307pub trait DirectSubmitTransport: Send + Sync {
308 async fn submit_direct(
310 &self,
311 tx_bytes: &[u8],
312 targets: &[LeaderTarget],
313 policy: RoutingPolicy,
314 config: &DirectSubmitConfig,
315 ) -> Result<LeaderTarget, SubmitTransportError>;
316}