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 hybrid_direct_attempts: usize,
72}
73
74impl DirectSubmitConfig {
75 #[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 #[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#[derive(Debug, Error, Clone, Eq, PartialEq)]
130pub enum SubmitTransportError {
131 #[error("transport configuration invalid: {message}")]
133 Config {
134 message: String,
136 },
137 #[error("transport failure: {message}")]
139 Failure {
140 message: String,
142 },
143}
144
145#[derive(Debug, Error)]
147pub enum SubmitError {
148 #[error("failed to build/sign transaction: {source}")]
150 Build {
151 source: BuilderError,
153 },
154 #[error("blockhash provider returned no recent blockhash")]
156 MissingRecentBlockhash,
157 #[error("failed to decode signed transaction bytes: {source}")]
159 DecodeSignedBytes {
160 source: Box<bincode::ErrorKind>,
162 },
163 #[error("duplicate signature suppressed by dedupe window")]
165 DuplicateSignature,
166 #[error("rpc transport is not configured")]
168 MissingRpcTransport,
169 #[error("direct transport is not configured")]
171 MissingDirectTransport,
172 #[error("no direct targets resolved from leader/backups")]
174 NoDirectTargets,
175 #[error("direct submit failed: {source}")]
177 Direct {
178 source: SubmitTransportError,
180 },
181 #[error("rpc submit failed: {source}")]
183 Rpc {
184 source: SubmitTransportError,
186 },
187 #[error("internal synchronization failure: {message}")]
189 InternalSync {
190 message: String,
192 },
193}
194
195#[derive(Debug, Clone, Eq, PartialEq)]
197pub struct SubmitResult {
198 pub signature: Option<Signature>,
200 pub mode: SubmitMode,
202 pub direct_target: Option<LeaderTarget>,
204 pub rpc_signature: Option<String>,
206 pub used_rpc_fallback: bool,
208}
209
210#[async_trait]
212pub trait RpcSubmitTransport: Send + Sync {
213 async fn submit_rpc(
215 &self,
216 tx_bytes: &[u8],
217 config: &RpcSubmitConfig,
218 ) -> Result<String, SubmitTransportError>;
219}
220
221#[async_trait]
223pub trait DirectSubmitTransport: Send + Sync {
224 async fn submit_direct(
226 &self,
227 tx_bytes: &[u8],
228 targets: &[LeaderTarget],
229 policy: RoutingPolicy,
230 config: &DirectSubmitConfig,
231 ) -> Result<LeaderTarget, SubmitTransportError>;
232}