1use std::{collections::HashMap, net::SocketAddr, sync::Arc};
7
8use iota_sdk_types::{TransactionDigest, TransactionEffectsDigest};
9use iota_types::{
10 base_types::*,
11 committee::*,
12 crypto::AuthorityPublicKeyBytes,
13 effects::{SignedTransactionEffects, TransactionEffectsAPI, TransactionEffectsExt},
14 error::{IotaError, IotaResult},
15 fp_ensure,
16 iota_system_state::IotaSystemState,
17 messages_checkpoint::{CheckpointRequest, CheckpointResponse},
18 messages_grpc::{
19 GetTxStatusRequest, HandleCapabilityNotificationRequestV1,
20 HandleCapabilityNotificationResponseV1, HandleCertificateRequestV1,
21 HandleCertificateResponseV1, ObjectInfoRequest, ObjectInfoResponse, SystemStateRequest,
22 TransactionInfoRequest, TransactionStatus, TxStatusUpdate, ValidatorHealthRequest,
23 ValidatorHealthResponse, VerifiedObjectInfoResponse,
24 },
25 messages_safe_client::PlainTransactionInfoResponse,
26 transaction::*,
27};
28use prometheus_filtered::{
29 Histogram, HistogramVec, IntCounterVec, Registry, core::GenericCounter,
30 register_histogram_vec_with_registry, register_int_counter_vec_with_registry,
31};
32use tap::TapFallible;
33use tracing::{debug, error, instrument};
34
35use crate::{authority_client::AuthorityAPI, epoch::committee_store::CommitteeStore};
36
37macro_rules! check_error {
38 ($address:expr, $cond:expr, $msg:expr) => {
39 $cond.tap_err(|err| {
40 if err.individual_error_indicates_epoch_change() {
41 debug!(?err, authority=?$address, "Not a real client error");
42 } else {
43 error!(?err, authority=?$address, $msg);
44 }
45 })
46 }
47}
48
49#[derive(Clone)]
50pub struct SafeClientMetricsBase {
51 total_requests_by_address_method: IntCounterVec,
52 total_responses_by_address_method: IntCounterVec,
53 latency: HistogramVec,
54}
55
56impl SafeClientMetricsBase {
57 pub fn new(registry: &Registry) -> Self {
58 Self {
59 total_requests_by_address_method: register_int_counter_vec_with_registry!(
60 "safe_client_total_requests_by_address_method",
61 "Total requests to validators group by address and method",
62 &["address", "method"],
63 registry,
64 )
65 .unwrap(),
66 total_responses_by_address_method: register_int_counter_vec_with_registry!(
67 "safe_client_total_responses_by_address_method",
68 "Total good (OK) responses from validators group by address and method",
69 &["address", "method"],
70 registry,
71 )
72 .unwrap(),
73 latency: register_histogram_vec_with_registry!(
75 "safe_client_latency",
76 "RPC latency observed by safe client aggregator, group by method",
77 &["method"],
78 iota_metrics::COARSE_LATENCY_SEC_BUCKETS.to_vec(),
79 registry,
80 )
81 .unwrap(),
82 }
83 }
84}
85
86#[derive(Clone)]
88pub struct SafeClientMetrics {
89 total_requests_handle_transaction_info_request:
90 GenericCounter<prometheus_filtered::core::AtomicU64>,
91 total_ok_responses_handle_transaction_info_request:
92 GenericCounter<prometheus_filtered::core::AtomicU64>,
93 total_requests_handle_object_info_request: GenericCounter<prometheus_filtered::core::AtomicU64>,
94 total_ok_responses_handle_object_info_request:
95 GenericCounter<prometheus_filtered::core::AtomicU64>,
96 handle_transaction_latency: Histogram,
97 handle_certificate_latency: Histogram,
98 handle_obj_info_latency: Histogram,
99 handle_tx_info_latency: Histogram,
100 submit_tx_latency: Histogram,
101 get_tx_status_latency: Histogram,
102 notify_capabilities_v2_latency: Histogram,
103 health_check_latency: Histogram,
104 get_checkpoint_v2_latency: Histogram,
105}
106
107impl SafeClientMetrics {
108 pub fn new(metrics_base: &SafeClientMetricsBase, validator_address: AuthorityName) -> Self {
109 let validator_address = validator_address.to_string();
110
111 let total_requests_handle_transaction_info_request = metrics_base
112 .total_requests_by_address_method
113 .with_label_values(&[
114 validator_address.as_str(),
115 "handle_transaction_info_request",
116 ]);
117 let total_ok_responses_handle_transaction_info_request = metrics_base
118 .total_responses_by_address_method
119 .with_label_values(&[
120 validator_address.as_str(),
121 "handle_transaction_info_request",
122 ]);
123
124 let total_requests_handle_object_info_request = metrics_base
125 .total_requests_by_address_method
126 .with_label_values(&[validator_address.as_str(), "handle_object_info_request"]);
127 let total_ok_responses_handle_object_info_request = metrics_base
128 .total_responses_by_address_method
129 .with_label_values(&[validator_address.as_str(), "handle_object_info_request"]);
130
131 let handle_transaction_latency = metrics_base
132 .latency
133 .with_label_values(&["handle_transaction"]);
134 let handle_certificate_latency = metrics_base
135 .latency
136 .with_label_values(&["handle_certificate"]);
137 let handle_obj_info_latency = metrics_base
138 .latency
139 .with_label_values(&["handle_object_info_request"]);
140 let handle_tx_info_latency = metrics_base
141 .latency
142 .with_label_values(&["handle_transaction_info_request"]);
143 let submit_tx_latency = metrics_base.latency.with_label_values(&["submit_tx"]);
144 let get_tx_status_latency = metrics_base.latency.with_label_values(&["get_tx_status"]);
145 let notify_capabilities_v2_latency = metrics_base
146 .latency
147 .with_label_values(&["notify_capabilities_v2"]);
148 let health_check_latency = metrics_base.latency.with_label_values(&["health_check"]);
149 let get_checkpoint_v2_latency = metrics_base
150 .latency
151 .with_label_values(&["get_checkpoint_v2"]);
152
153 Self {
154 total_requests_handle_transaction_info_request,
155 total_ok_responses_handle_transaction_info_request,
156 total_requests_handle_object_info_request,
157 total_ok_responses_handle_object_info_request,
158 handle_transaction_latency,
159 handle_certificate_latency,
160 handle_obj_info_latency,
161 handle_tx_info_latency,
162 submit_tx_latency,
163 get_tx_status_latency,
164 notify_capabilities_v2_latency,
165 health_check_latency,
166 get_checkpoint_v2_latency,
167 }
168 }
169
170 pub fn new_for_tests(validator_address: AuthorityName) -> Self {
171 let registry = Registry::new();
172 let metrics_base = SafeClientMetricsBase::new(®istry);
173 Self::new(&metrics_base, validator_address)
174 }
175}
176
177#[derive(Clone)]
180pub struct SafeClient<C> {
181 authority_client: C,
182 committee_store: Arc<CommitteeStore>,
183 address: AuthorityPublicKeyBytes,
184 metrics: SafeClientMetrics,
185}
186
187impl<C> SafeClient<C> {
188 pub fn new(
189 authority_client: C,
190 committee_store: Arc<CommitteeStore>,
191 address: AuthorityPublicKeyBytes,
192 metrics: SafeClientMetrics,
193 ) -> Self {
194 Self {
195 authority_client,
196 committee_store,
197 address,
198 metrics,
199 }
200 }
201
202 pub fn authority_client(&self) -> &C {
203 &self.authority_client
204 }
205
206 #[cfg(test)]
207 pub fn authority_client_mut(&mut self) -> &mut C {
208 &mut self.authority_client
209 }
210
211 fn get_committee(&self, epoch_id: &EpochId) -> IotaResult<Arc<Committee>> {
212 self.committee_store
213 .get_committee(epoch_id)?
214 .ok_or(IotaError::MissingCommitteeAtEpoch(*epoch_id))
215 }
216
217 fn check_signed_effects_plain(
218 &self,
219 digest: &TransactionDigest,
220 signed_effects: SignedTransactionEffects,
221 expected_effects_digest: Option<&TransactionEffectsDigest>,
222 ) -> IotaResult<SignedTransactionEffects> {
223 fp_ensure!(
225 signed_effects.auth_sig().authority == self.address,
226 IotaError::ByzantineAuthoritySuspicion {
227 authority: self.address,
228 reason: format!(
229 "Unexpected validator address in the signed effects signature: {:?}",
230 signed_effects.auth_sig().authority
231 ),
232 }
233 );
234 fp_ensure!(
236 signed_effects.data().transaction_digest() == digest,
237 IotaError::ByzantineAuthoritySuspicion {
238 authority: self.address,
239 reason: "Unexpected tx digest in the signed effects".to_string()
240 }
241 );
242 if let Some(effects_digest) = expected_effects_digest {
244 fp_ensure!(
245 signed_effects.digest() == effects_digest,
246 IotaError::ByzantineAuthoritySuspicion {
247 authority: self.address,
248 reason: "Effects digest does not match with expected digest".to_string()
249 }
250 );
251 }
252 self.get_committee(&signed_effects.epoch())?;
253 Ok(signed_effects)
254 }
255
256 fn check_transaction_info(
257 &self,
258 digest: &TransactionDigest,
259 transaction: TransactionEnvelope,
260 status: TransactionStatus,
261 ) -> IotaResult<PlainTransactionInfoResponse> {
262 fp_ensure!(
263 digest == transaction.digest(),
264 IotaError::ByzantineAuthoritySuspicion {
265 authority: self.address,
266 reason: "Signed transaction digest does not match with expected digest".to_string()
267 }
268 );
269 match status {
270 TransactionStatus::Signed(signed) => {
271 self.get_committee(&signed.epoch)?;
272 Ok(PlainTransactionInfoResponse::Signed(
273 SignedTransaction::new_from_data_and_sig(transaction.into_data(), signed),
274 ))
275 }
276 TransactionStatus::Executed(cert_opt, effects, events) => {
277 let signed_effects = self.check_signed_effects_plain(digest, effects, None)?;
278 match cert_opt {
279 Some(cert) => {
280 let committee = self.get_committee(&cert.epoch)?;
281 let ct = CertifiedTransaction::new_from_data_and_sig(
282 transaction.into_data(),
283 cert,
284 );
285 ct.verify_committee_sigs_only(&committee).map_err(|e| {
286 IotaError::FailedToVerifyTxCertWithExecutedEffects {
287 validator_name: self.address,
288 error: e.to_string(),
289 }
290 })?;
291 Ok(PlainTransactionInfoResponse::ExecutedWithCert(
292 ct,
293 signed_effects,
294 events,
295 ))
296 }
297 None => Ok(PlainTransactionInfoResponse::ExecutedWithoutCert(
298 transaction,
299 signed_effects,
300 events,
301 )),
302 }
303 }
304 }
305 }
306
307 fn check_object_response(
308 &self,
309 request: &ObjectInfoRequest,
310 response: ObjectInfoResponse,
311 ) -> IotaResult<VerifiedObjectInfoResponse> {
312 let ObjectInfoResponse {
313 object,
314 layout: _,
315 lock_for_debugging: _,
316 } = response;
317
318 fp_ensure!(
319 request.object_id == object.id(),
320 IotaError::ByzantineAuthoritySuspicion {
321 authority: self.address,
322 reason: "Object id mismatch in the response".to_string()
323 }
324 );
325
326 Ok(VerifiedObjectInfoResponse { object })
327 }
328
329 pub fn address(&self) -> &AuthorityPublicKeyBytes {
330 &self.address
331 }
332}
333
334impl<C> SafeClient<C>
335where
336 C: AuthorityAPI + Send + Sync + 'static,
337{
338 pub async fn handle_transaction(
340 &self,
341 transaction: TransactionEnvelope,
342 client_addr: Option<SocketAddr>,
343 ) -> Result<PlainTransactionInfoResponse, IotaError> {
344 let _timer = self.metrics.handle_transaction_latency.start_timer();
345 let digest = *transaction.digest();
346 let response = self
347 .authority_client
348 .handle_transaction(transaction.clone(), client_addr)
349 .await?;
350 let response = check_error!(
351 self.address,
352 self.check_transaction_info(&digest, transaction, response.status),
353 "Client error in handle_transaction"
354 )?;
355 Ok(response)
356 }
357
358 fn verify_certificate_response_v1(
359 &self,
360 digest: &TransactionDigest,
361 HandleCertificateResponseV1 {
362 signed_effects,
363 events,
364 input_objects,
365 output_objects,
366 auxiliary_data,
367 }: HandleCertificateResponseV1,
368 ) -> IotaResult<HandleCertificateResponseV1> {
369 let signed_effects = self.check_signed_effects_plain(digest, signed_effects, None)?;
370
371 match (&events, signed_effects.events_digest()) {
373 (None, None) | (None, Some(_)) => {}
374 (Some(events), None) => {
375 if !events.is_empty() {
376 return Err(IotaError::ByzantineAuthoritySuspicion {
377 authority: self.address,
378 reason: "Returned events but no event digest present in the signed effects"
379 .to_string(),
380 });
381 }
382 }
383 (Some(events), Some(events_digest)) => {
384 fp_ensure!(
385 &events.digest() == events_digest,
386 IotaError::ByzantineAuthoritySuspicion {
387 authority: self.address,
388 reason: "Returned events don't match events digest in the signed effects"
389 .to_string()
390 }
391 );
392 }
393 }
394
395 if let Some(input_objects) = &input_objects {
397 let expected: HashMap<_, _> = signed_effects
398 .old_object_metadata()
399 .into_iter()
400 .map(|old| (old.reference().object_id, *old.reference()))
401 .collect();
402
403 for object in input_objects {
404 let object_ref = object.object_ref();
405 if expected
406 .get(&object_ref.object_id)
407 .is_none_or(|expect| &object_ref != expect)
408 {
409 return Err(IotaError::ByzantineAuthoritySuspicion {
410 authority: self.address,
411 reason: "Returned input object that wasn't present in the signed effects"
412 .to_string(),
413 });
414 }
415 }
416 }
417
418 if let Some(output_objects) = &output_objects {
420 let expected: HashMap<_, _> = signed_effects
421 .all_changed_objects()
422 .into_iter()
423 .map(|(changed, _)| (changed.reference().object_id, *changed.reference()))
424 .collect();
425
426 for object in output_objects {
427 let object_ref = object.object_ref();
428 if expected
429 .get(&object_ref.object_id)
430 .is_none_or(|expect| &object_ref != expect)
431 {
432 return Err(IotaError::ByzantineAuthoritySuspicion {
433 authority: self.address,
434 reason: "Returned output object that wasn't present in the signed effects"
435 .to_string(),
436 });
437 }
438 }
439 }
440
441 Ok(HandleCertificateResponseV1 {
442 signed_effects,
443 events,
444 input_objects,
445 output_objects,
446 auxiliary_data,
447 })
448 }
449
450 pub async fn handle_certificate_v1(
452 &self,
453 request: HandleCertificateRequestV1,
454 client_addr: Option<SocketAddr>,
455 ) -> Result<HandleCertificateResponseV1, IotaError> {
456 let digest = *request.certificate.digest();
457 let _timer = self.metrics.handle_certificate_latency.start_timer();
458 let response = self
459 .authority_client
460 .handle_certificate_v1(request, client_addr)
461 .await?;
462
463 let verified = check_error!(
464 self.address,
465 self.verify_certificate_response_v1(&digest, response),
466 "Client error in handle_certificate"
467 )?;
468 Ok(verified)
469 }
470
471 pub async fn handle_object_info_request(
472 &self,
473 request: ObjectInfoRequest,
474 ) -> Result<VerifiedObjectInfoResponse, IotaError> {
475 self.metrics.total_requests_handle_object_info_request.inc();
476
477 let _timer = self.metrics.handle_obj_info_latency.start_timer();
478 let response = self
479 .authority_client
480 .handle_object_info_request(request.clone())
481 .await?;
482 let response = self
483 .check_object_response(&request, response)
484 .tap_err(|err| error!(?err, authority=?self.address, "Client error in handle_object_info_request"))?;
485
486 self.metrics
487 .total_ok_responses_handle_object_info_request
488 .inc();
489 Ok(response)
490 }
491
492 #[instrument(level = "trace", skip_all, fields(authority = ?self.address.concise()))]
494 pub async fn handle_transaction_info_request(
495 &self,
496 request: TransactionInfoRequest,
497 ) -> Result<PlainTransactionInfoResponse, IotaError> {
498 self.metrics
499 .total_requests_handle_transaction_info_request
500 .inc();
501
502 let _timer = self.metrics.handle_tx_info_latency.start_timer();
503
504 let transaction_info = self
505 .authority_client
506 .handle_transaction_info_request(request.clone())
507 .await?;
508
509 let transaction = TransactionEnvelope::new(transaction_info.transaction);
510 let transaction_info = self.check_transaction_info(
511 &request.transaction_digest,
512 transaction,
513 transaction_info.status,
514 ).tap_err(|err| {
515 error!(?err, authority=?self.address, "Client error in handle_transaction_info_request");
516 })?;
517 self.metrics
518 .total_ok_responses_handle_transaction_info_request
519 .inc();
520 Ok(transaction_info)
521 }
522
523 #[instrument(level = "trace", skip_all, fields(authority = ?self.address.concise()))]
524 pub async fn handle_system_state_object(&self) -> Result<IotaSystemState, IotaError> {
525 self.authority_client
526 .handle_system_state_object(SystemStateRequest { _unused: false })
527 .await
528 }
529
530 #[instrument(level = "trace", skip_all, fields(authority = ?self.address.concise()))]
533 pub async fn submit_tx(
534 &self,
535 transactions: Vec<TransactionEnvelope>,
536 client_addr: Option<SocketAddr>,
537 ) -> Result<Vec<(TransactionDigest, TxStatusUpdate)>, IotaError> {
538 let _timer = self.metrics.submit_tx_latency.start_timer();
539 check_error!(
540 self.address,
541 self.authority_client
542 .submit_tx(transactions, client_addr)
543 .await,
544 "Client error in submit_tx"
545 )
546 }
547
548 #[instrument(level = "trace", skip_all, fields(authority = ?self.address.concise()))]
549 pub async fn get_tx_status(
550 &self,
551 request: GetTxStatusRequest,
552 client_addr: Option<SocketAddr>,
553 ) -> Result<Vec<(TransactionDigest, TxStatusUpdate)>, IotaError> {
554 let _timer = self.metrics.get_tx_status_latency.start_timer();
555 check_error!(
556 self.address,
557 self.authority_client
558 .get_tx_status(request, client_addr)
559 .await,
560 "Client error in get_tx_status"
561 )
562 }
563
564 #[instrument(level = "trace", skip_all, fields(authority = ?self.address.concise()))]
565 pub async fn notify_capabilities_v2(
566 &self,
567 request: HandleCapabilityNotificationRequestV1,
568 ) -> Result<HandleCapabilityNotificationResponseV1, IotaError> {
569 let _timer = self.metrics.notify_capabilities_v2_latency.start_timer();
570 check_error!(
571 self.address,
572 self.authority_client.notify_capabilities_v2(request).await,
573 "Client error in notify_capabilities_v2"
574 )
575 }
576
577 #[instrument(level = "trace", skip_all, fields(authority = ?self.address.concise()))]
578 pub async fn health_check(
579 &self,
580 request: ValidatorHealthRequest,
581 ) -> Result<ValidatorHealthResponse, IotaError> {
582 let _timer = self.metrics.health_check_latency.start_timer();
583 check_error!(
584 self.address,
585 self.authority_client.health_check(request).await,
586 "Client error in health_check"
587 )
588 }
589
590 #[instrument(level = "trace", skip_all, fields(authority = ?self.address.concise()))]
593 pub async fn get_checkpoint_v2(
594 &self,
595 request: CheckpointRequest,
596 ) -> Result<CheckpointResponse, IotaError> {
597 let _timer = self.metrics.get_checkpoint_v2_latency.start_timer();
598 check_error!(
599 self.address,
600 self.authority_client.get_checkpoint_v2(request).await,
601 "Client error in get_checkpoint_v2"
602 )
603 }
604}