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 assert!(
187 !new_errors.is_empty(),
188 "NonRecoverableTransactionError should have at least one non-retryable error"
189 );
190
191 let mut error_list = vec![];
192 for err in new_errors.iter() {
193 error_list.push(format!("- {err}"));
194 }
195
196 format!(
197 "Transaction execution failed due to issues with transaction inputs, please review the errors and try again:\n{}",
198 error_list.join("\n")
199 )
200 }
201 QuorumDriverError::QuorumDriverInternal { .. } => {
202 "Internal error occurred while executing transaction.".to_string()
203 }
204 }
205 }
206}
207
208pub type GroupedErrors = Vec<(IotaError, StakeUnit, Vec<ConciseAuthorityPublicKeyBytes>)>;
209
210#[derive(Serialize, Deserialize, Clone, Debug)]
211pub enum ExecuteTransactionRequestType {
212 WaitForEffectsCert,
213 WaitForLocalExecution,
214}
215
216#[derive(Serialize, Deserialize, Clone, Debug)]
217pub enum EffectsFinalityInfo {
218 Certified(AuthorityStrongQuorumSignInfo),
219 Checkpointed(EpochId, CheckpointSequenceNumber),
220 QuorumExecuted(EpochId),
223
224 UncertifiedSingleValidator(EpochId),
228}
229
230pub type IsTransactionExecutedLocally = bool;
235
236#[derive(Debug, Clone)]
237pub struct QuorumDriverResponse {
238 pub effects_cert: VerifiedCertifiedTransactionEffects,
239 pub events: Option<TransactionEvents>,
241 pub input_objects: Option<Vec<Object>>,
243 pub output_objects: Option<Vec<Object>>,
245 pub auxiliary_data: Option<Vec<u8>>,
246}
247
248#[derive(Serialize, Deserialize, Clone, Debug)]
249pub struct ExecuteTransactionRequestV1 {
250 pub transaction: TransactionEnvelope,
251
252 pub include_events: bool,
253 pub include_input_objects: bool,
254 pub include_output_objects: bool,
255 pub include_auxiliary_data: bool,
256}
257
258impl ExecuteTransactionRequestV1 {
259 pub fn new<T: Into<TransactionEnvelope>>(transaction: T) -> Self {
260 Self {
261 transaction: transaction.into(),
262 include_events: true,
263 include_input_objects: false,
264 include_output_objects: false,
265 include_auxiliary_data: false,
266 }
267 }
268}
269
270#[derive(Serialize, Deserialize, Clone, Debug)]
271pub struct ExecuteTransactionResponseV1 {
272 pub effects: FinalizedEffects,
273
274 pub events: Option<TransactionEvents>,
275 pub input_objects: Option<Vec<Object>>,
277 pub output_objects: Option<Vec<Object>>,
279 pub auxiliary_data: Option<Vec<u8>>,
280}
281
282#[derive(Serialize, Deserialize, Clone, Debug)]
283pub struct FinalizedEffects {
284 pub effects: TransactionEffects,
285 pub finality_info: EffectsFinalityInfo,
286}
287
288impl FinalizedEffects {
289 pub fn new_from_effects_cert(effects_cert: CertifiedTransactionEffects) -> Self {
290 let (data, sig) = effects_cert.into_data_and_sig();
291 Self {
292 effects: data,
293 finality_info: EffectsFinalityInfo::Certified(sig),
294 }
295 }
296
297 pub fn epoch(&self) -> EpochId {
298 match &self.finality_info {
299 EffectsFinalityInfo::Certified(cert) => cert.epoch,
300 EffectsFinalityInfo::Checkpointed(epoch, _)
301 | EffectsFinalityInfo::QuorumExecuted(epoch)
302 | EffectsFinalityInfo::UncertifiedSingleValidator(epoch) => *epoch,
303 }
304 }
305}