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}.")]
42 InvalidTransaction(IotaError),
43 #[error(
44 "Failed to sign transaction by a quorum of validators because of locked objects: {conflicting_txes:?}"
45 )]
46 ObjectsDoubleUsed {
47 conflicting_txes:
48 BTreeMap<TransactionDigest, (Vec<(AuthorityName, ObjectReference)>, StakeUnit)>,
49 },
50 #[error("Transaction timed out before reaching finality")]
51 TimeoutBeforeFinality,
52 #[error(
53 "Transaction failed to reach finality with transient error after {total_attempts} attempts."
54 )]
55 FailedWithTransientErrorAfterMaximumAttempts { total_attempts: u32 },
56 #[error("{NON_RECOVERABLE_ERROR_MSG}: {errors:?}.")]
57 NonRecoverableTransactionError { errors: GroupedErrors },
58 #[error(
59 "Transaction is not processed because {overloaded_stake} of validators by stake are overloaded with certificates pending execution."
60 )]
61 SystemOverload {
62 overloaded_stake: StakeUnit,
63 errors: GroupedErrors,
64 },
65 #[error("Transaction is already finalized but with different user signatures")]
66 TxAlreadyFinalizedWithDifferentUserSignatures,
67 #[error(
68 "Transaction is not processed because {overload_stake} of validators are overloaded and asked client to retry after {retry_after_secs}."
69 )]
70 SystemOverloadRetryAfter {
71 overload_stake: StakeUnit,
72 errors: GroupedErrors,
73 retry_after_secs: u64,
74 },
75}
76
77impl QuorumDriverError {
78 pub fn to_error_message(&self) -> String {
79 match self {
80 QuorumDriverError::InvalidUserSignature(err) => {
81 format!("Invalid user signature: {err}")
82 }
83 QuorumDriverError::InvalidTransaction(err) => {
84 format!("Invalid transaction: {err}")
85 }
86 QuorumDriverError::TxAlreadyFinalizedWithDifferentUserSignatures => {
87 "The transaction is already finalized but with different user signatures"
88 .to_string()
89 }
90 QuorumDriverError::TimeoutBeforeFinality
91 | QuorumDriverError::FailedWithTransientErrorAfterMaximumAttempts { .. }
92 | QuorumDriverError::SystemOverload { .. }
93 | QuorumDriverError::SystemOverloadRetryAfter { .. } => self.to_string(),
94 QuorumDriverError::ObjectsDoubleUsed { conflicting_txes } => {
95 let weights: Vec<u64> =
96 conflicting_txes.values().map(|(_, stake)| *stake).collect();
97 let remaining: u64 = TOTAL_VOTING_POWER - weights.iter().sum::<u64>();
98
99 let reason = if weights.iter().all(|w| remaining + w < QUORUM_THRESHOLD) {
101 "equivocated until the next epoch"
102 } else {
103 "reserved for another transaction"
104 };
105
106 format!(
107 "Failed to sign transaction by a quorum of validators because one or more of its objects is {}. Other transactions locking these objects:\n{}",
108 reason,
109 conflicting_txes
110 .iter()
111 .sorted_by(|(_, (_, a)), (_, (_, b))| b.cmp(a))
112 .map(|(digest, (_, stake))| format!(
113 "- {} (stake {}.{})",
114 digest,
115 stake / 100,
116 stake % 100,
117 ))
118 .join("\n"),
119 )
120 }
121 QuorumDriverError::NonRecoverableTransactionError { errors } => {
122 let new_errors: Vec<String> = errors
123 .iter()
124 .sorted_by(|(_, a, _), (_, b, _)| b.cmp(a))
127 .filter_map(|(err, _, _)| {
128 match &err {
129 IotaError::UserInput { error } => Some(error.to_string()),
140 _ => {
141 if err.is_retryable().0 {
142 None
143 } else {
144 Some(err.to_string())
145 }
146 }
147 }
148 })
149 .collect();
150
151 assert!(
152 !new_errors.is_empty(),
153 "NonRecoverableTransactionError should have at least one non-retryable error"
154 );
155
156 let mut error_list = vec![];
157 for err in new_errors.iter() {
158 error_list.push(format!("- {err}"));
159 }
160
161 format!(
162 "Transaction execution failed due to issues with transaction inputs, please review the errors and try again:\n{}",
163 error_list.join("\n")
164 )
165 }
166 QuorumDriverError::QuorumDriverInternal { .. } => {
167 "Internal error occurred while executing transaction.".to_string()
168 }
169 }
170 }
171}
172
173pub type GroupedErrors = Vec<(IotaError, StakeUnit, Vec<ConciseAuthorityPublicKeyBytes>)>;
174
175#[derive(Serialize, Deserialize, Clone, Debug)]
176pub enum ExecuteTransactionRequestType {
177 WaitForEffectsCert,
178 WaitForLocalExecution,
179}
180
181#[derive(Serialize, Deserialize, Clone, Debug)]
182pub enum EffectsFinalityInfo {
183 Certified(AuthorityStrongQuorumSignInfo),
184 Checkpointed(EpochId, CheckpointSequenceNumber),
185 QuorumExecuted(EpochId),
188
189 UncertifiedSingleValidator(EpochId),
193}
194
195pub type IsTransactionExecutedLocally = bool;
200
201#[derive(Debug, Clone)]
202pub struct QuorumDriverResponse {
203 pub effects_cert: VerifiedCertifiedTransactionEffects,
204 pub events: Option<TransactionEvents>,
206 pub input_objects: Option<Vec<Object>>,
208 pub output_objects: Option<Vec<Object>>,
210 pub auxiliary_data: Option<Vec<u8>>,
211}
212
213#[derive(Serialize, Deserialize, Clone, Debug)]
214pub struct ExecuteTransactionRequestV1 {
215 pub transaction: TransactionEnvelope,
216
217 pub include_events: bool,
218 pub include_input_objects: bool,
219 pub include_output_objects: bool,
220 pub include_auxiliary_data: bool,
221}
222
223impl ExecuteTransactionRequestV1 {
224 pub fn new<T: Into<TransactionEnvelope>>(transaction: T) -> Self {
225 Self {
226 transaction: transaction.into(),
227 include_events: true,
228 include_input_objects: false,
229 include_output_objects: false,
230 include_auxiliary_data: false,
231 }
232 }
233}
234
235#[derive(Serialize, Deserialize, Clone, Debug)]
236pub struct ExecuteTransactionResponseV1 {
237 pub effects: FinalizedEffects,
238
239 pub events: Option<TransactionEvents>,
240 pub input_objects: Option<Vec<Object>>,
242 pub output_objects: Option<Vec<Object>>,
244 pub auxiliary_data: Option<Vec<u8>>,
245}
246
247#[derive(Serialize, Deserialize, Clone, Debug)]
248pub struct FinalizedEffects {
249 pub effects: TransactionEffects,
250 pub finality_info: EffectsFinalityInfo,
251}
252
253impl FinalizedEffects {
254 pub fn new_from_effects_cert(effects_cert: CertifiedTransactionEffects) -> Self {
255 let (data, sig) = effects_cert.into_data_and_sig();
256 Self {
257 effects: data,
258 finality_info: EffectsFinalityInfo::Certified(sig),
259 }
260 }
261
262 pub fn epoch(&self) -> EpochId {
263 match &self.finality_info {
264 EffectsFinalityInfo::Certified(cert) => cert.epoch,
265 EffectsFinalityInfo::Checkpointed(epoch, _)
266 | EffectsFinalityInfo::QuorumExecuted(epoch)
267 | EffectsFinalityInfo::UncertifiedSingleValidator(epoch) => *epoch,
268 }
269 }
270}