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