1use std::collections::BTreeMap;
7
8use iota_sdk_types::{ObjectReference, TransactionDigest, TransactionEffects, TransactionEvents};
9use itertools::Itertools;
10use serde::{Deserialize, Serialize};
11use strum::AsRefStr;
12use thiserror::Error;
13
14use crate::{
15 base_types::{AuthorityName, EpochId},
16 committee::{QUORUM_THRESHOLD, StakeUnit, TOTAL_VOTING_POWER},
17 crypto::{AuthorityStrongQuorumSignInfo, ConciseAuthorityPublicKeyBytes},
18 effects::{CertifiedTransactionEffects, VerifiedCertifiedTransactionEffects},
19 error::IotaError,
20 messages_checkpoint::CheckpointSequenceNumber,
21 object::Object,
22 transaction::TransactionEnvelope,
23};
24
25pub type QuorumDriverResult = Result<QuorumDriverResponse, QuorumDriverError>;
26
27pub type QuorumDriverEffectsQueueResult =
28 Result<(TransactionEnvelope, QuorumDriverResponse), (TransactionDigest, QuorumDriverError)>;
29
30pub const NON_RECOVERABLE_ERROR_MSG: &str =
31 "Transaction has non recoverable errors from at least 1/3 of validators";
32
33#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Error, Hash, AsRefStr)]
36pub enum QuorumDriverError {
37 #[error("QuorumDriver internal error: {0}.")]
38 QuorumDriverInternal(IotaError),
39 #[error("Invalid user signature: {0}.")]
40 InvalidUserSignature(IotaError),
41 #[error("Invalid transaction: {0}.")]
44 InvalidTransaction(IotaError),
45 #[error(
46 "Failed to sign transaction by a quorum of validators because of locked objects: {conflicting_txes:?}"
47 )]
48 ObjectsDoubleUsed {
49 conflicting_txes:
50 BTreeMap<TransactionDigest, (Vec<(AuthorityName, ObjectReference)>, StakeUnit)>,
51 },
52 #[error("Transaction timed out before reaching finality")]
53 TimeoutBeforeFinality,
54 #[error(
55 "Transaction failed to reach finality with transient error after {total_attempts} attempts."
56 )]
57 FailedWithTransientErrorAfterMaximumAttempts { total_attempts: u32 },
58 #[error("{NON_RECOVERABLE_ERROR_MSG}: {errors:?}.")]
59 NonRecoverableTransactionError { errors: GroupedErrors },
60 #[error(
61 "Transaction is not processed because {overloaded_stake} of validators by stake are overloaded with certificates pending execution."
62 )]
63 SystemOverload {
64 overloaded_stake: StakeUnit,
65 errors: GroupedErrors,
66 },
67 #[error("Transaction is already finalized but with different user signatures")]
68 TxAlreadyFinalizedWithDifferentUserSignatures,
69 #[error(
70 "Transaction is not processed because {overload_stake} of validators are overloaded and asked client to retry after {retry_after_secs}."
71 )]
72 SystemOverloadRetryAfter {
73 overload_stake: StakeUnit,
74 errors: GroupedErrors,
75 retry_after_secs: u64,
76 },
77 #[error("Invalid transaction: {0}.")]
82 RejectedByValidators(IotaError),
83}
84
85impl QuorumDriverError {
86 pub fn reason(&self) -> &'static str {
91 match self {
92 QuorumDriverError::QuorumDriverInternal(_) => "internal",
93 QuorumDriverError::InvalidUserSignature(_) => "invalid_user_signature",
94 QuorumDriverError::InvalidTransaction(_) => "invalid_transaction",
95 QuorumDriverError::RejectedByValidators(_) => "rejected_by_validators",
96 QuorumDriverError::ObjectsDoubleUsed { .. } => "objects_double_used",
97 QuorumDriverError::TimeoutBeforeFinality => "timeout_before_finality",
98 QuorumDriverError::FailedWithTransientErrorAfterMaximumAttempts { .. } => {
99 "failed_with_transient_error_after_maximum_attempts"
100 }
101 QuorumDriverError::NonRecoverableTransactionError { .. } => {
102 "non_recoverable_transaction_error"
103 }
104 QuorumDriverError::SystemOverload { .. } => "system_overload",
105 QuorumDriverError::TxAlreadyFinalizedWithDifferentUserSignatures => {
106 "tx_already_finalized_with_different_user_signatures"
107 }
108 QuorumDriverError::SystemOverloadRetryAfter { .. } => "system_overload_retry_after",
109 }
110 }
111
112 pub fn to_error_message(&self) -> String {
113 match self {
114 QuorumDriverError::InvalidUserSignature(err) => {
115 format!("Invalid user signature: {err}")
116 }
117 QuorumDriverError::InvalidTransaction(err)
118 | QuorumDriverError::RejectedByValidators(err) => {
119 format!("Invalid transaction: {err}")
120 }
121 QuorumDriverError::TxAlreadyFinalizedWithDifferentUserSignatures => {
122 "The transaction is already finalized but with different user signatures"
123 .to_string()
124 }
125 QuorumDriverError::TimeoutBeforeFinality
126 | QuorumDriverError::FailedWithTransientErrorAfterMaximumAttempts { .. }
127 | QuorumDriverError::SystemOverload { .. }
128 | QuorumDriverError::SystemOverloadRetryAfter { .. } => self.to_string(),
129 QuorumDriverError::ObjectsDoubleUsed { conflicting_txes } => {
130 let weights: Vec<u64> =
131 conflicting_txes.values().map(|(_, stake)| *stake).collect();
132 let remaining: u64 = TOTAL_VOTING_POWER - weights.iter().sum::<u64>();
133
134 let reason = if weights.iter().all(|w| remaining + w < QUORUM_THRESHOLD) {
136 "equivocated until the next epoch"
137 } else {
138 "reserved for another transaction"
139 };
140
141 format!(
142 "Failed to sign transaction by a quorum of validators because one or more of its objects is {}. Other transactions locking these objects:\n{}",
143 reason,
144 conflicting_txes
145 .iter()
146 .sorted_by(|(_, (_, a)), (_, (_, b))| b.cmp(a))
147 .map(|(digest, (_, stake))| format!(
148 "- {} (stake {}.{})",
149 digest,
150 stake / 100,
151 stake % 100,
152 ))
153 .join("\n"),
154 )
155 }
156 QuorumDriverError::NonRecoverableTransactionError { errors } => {
157 let new_errors: Vec<String> = errors
158 .iter()
159 .sorted_by(|(_, a, _), (_, b, _)| b.cmp(a))
162 .filter_map(|(err, _, _)| {
163 match &err {
164 IotaError::UserInput { error } => Some(error.to_string()),
175 _ => {
176 if err.is_retryable().0 {
177 None
178 } else {
179 Some(err.to_string())
180 }
181 }
182 }
183 })
184 .collect();
185
186 if new_errors.is_empty() {
190 let error_list = errors.iter().map(|(err, _, _)| err.to_string()).join(", ");
191 return format!("{NON_RECOVERABLE_ERROR_MSG}: {error_list}.");
192 }
193
194 let mut error_list = vec![];
195 for err in new_errors.iter() {
196 error_list.push(format!("- {err}"));
197 }
198
199 format!(
200 "Transaction execution failed due to issues with transaction inputs, please review the errors and try again:\n{}",
201 error_list.join("\n")
202 )
203 }
204 QuorumDriverError::QuorumDriverInternal { .. } => {
205 "Internal error occurred while executing transaction.".to_string()
206 }
207 }
208 }
209}
210
211pub type GroupedErrors = Vec<(IotaError, StakeUnit, Vec<ConciseAuthorityPublicKeyBytes>)>;
212
213#[derive(Serialize, Deserialize, Clone, Debug)]
214pub enum ExecuteTransactionRequestType {
215 WaitForEffectsCert,
216 WaitForLocalExecution,
217}
218
219#[derive(Serialize, Deserialize, Clone, Debug)]
220pub enum EffectsFinalityInfo {
221 Certified(AuthorityStrongQuorumSignInfo),
222 Checkpointed(EpochId, CheckpointSequenceNumber),
223 QuorumExecuted(EpochId),
226
227 UncertifiedSingleValidator(EpochId),
231}
232
233pub type IsTransactionExecutedLocally = bool;
238
239#[derive(Debug, Clone)]
240pub struct QuorumDriverResponse {
241 pub effects_cert: VerifiedCertifiedTransactionEffects,
242 pub events: Option<TransactionEvents>,
244 pub input_objects: Option<Vec<Object>>,
246 pub output_objects: Option<Vec<Object>>,
248 pub auxiliary_data: Option<Vec<u8>>,
249}
250
251#[derive(Serialize, Deserialize, Clone, Debug)]
252pub struct ExecuteTransactionRequestV1 {
253 pub transaction: TransactionEnvelope,
254
255 pub include_events: bool,
256 pub include_input_objects: bool,
257 pub include_output_objects: bool,
258 pub include_auxiliary_data: bool,
259}
260
261impl ExecuteTransactionRequestV1 {
262 pub fn new<T: Into<TransactionEnvelope>>(transaction: T) -> Self {
263 Self {
264 transaction: transaction.into(),
265 include_events: true,
266 include_input_objects: false,
267 include_output_objects: false,
268 include_auxiliary_data: false,
269 }
270 }
271}
272
273#[derive(Serialize, Deserialize, Clone, Debug)]
274pub struct ExecuteTransactionResponseV1 {
275 pub effects: FinalizedEffects,
276
277 pub events: Option<TransactionEvents>,
278 pub input_objects: Option<Vec<Object>>,
280 pub output_objects: Option<Vec<Object>>,
282 pub auxiliary_data: Option<Vec<u8>>,
283}
284
285#[derive(Serialize, Deserialize, Clone, Debug)]
286pub struct FinalizedEffects {
287 pub effects: TransactionEffects,
288 pub finality_info: EffectsFinalityInfo,
289}
290
291impl FinalizedEffects {
292 pub fn new_from_effects_cert(effects_cert: CertifiedTransactionEffects) -> Self {
293 let (data, sig) = effects_cert.into_data_and_sig();
294 Self {
295 effects: data,
296 finality_info: EffectsFinalityInfo::Certified(sig),
297 }
298 }
299
300 pub fn epoch(&self) -> EpochId {
301 match &self.finality_info {
302 EffectsFinalityInfo::Certified(cert) => cert.epoch,
303 EffectsFinalityInfo::Checkpointed(epoch, _)
304 | EffectsFinalityInfo::QuorumExecuted(epoch)
305 | EffectsFinalityInfo::UncertifiedSingleValidator(epoch) => *epoch,
306 }
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 #[test]
317 fn non_recoverable_error_rendering_survives_empty_filter() {
318 let error = QuorumDriverError::NonRecoverableTransactionError {
319 errors: vec![(
320 IotaError::ValidatorOverloadedRetryAfter {
321 retry_after_secs: 1,
322 },
323 0,
324 vec![],
325 )],
326 };
327 assert!(error.to_error_message().contains(NON_RECOVERABLE_ERROR_MSG));
328 }
329}