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