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