Bug: [REFACTOR] send `tenant-id` and `request-id` in grpc headers




diff --git a/crates/common_utils/src/consts.rs b/crates/common_utils/src/consts.rs
index 39e5483700a..3b437b703be 100644
--- a/crates/common_utils/src/consts.rs
+++ b/crates/common_utils/src/consts.rs
@@ -146,3 +146,6 @@ pub const CONNECTOR_TRANSACTION_ID_HASH_BYTES: usize = 25;
 /// Apple Pay validation url
 pub const APPLEPAY_VALIDATION_URL: &str =
     "https://apple-pay-gateway-cert.apple.com/paymentservices/startSession";
+
+/// Request ID
+pub const X_REQUEST_ID: &str = "x-request-id";
diff --git a/crates/external_services/src/grpc_client.rs b/crates/external_services/src/grpc_client.rs
index 8981a1094d6..404685025ed 100644
--- a/crates/external_services/src/grpc_client.rs
+++ b/crates/external_services/src/grpc_client.rs
@@ -6,6 +6,8 @@ pub mod dynamic_routing;
 pub mod health_check_client;
 use std::{fmt::Debug, sync::Arc};
 
+#[cfg(feature = "dynamic_routing")]
+use common_utils::consts;
 #[cfg(feature = "dynamic_routing")]
 use dynamic_routing::{DynamicRoutingClientConfig, RoutingStrategy};
 #[cfg(feature = "dynamic_routing")]
@@ -16,6 +18,8 @@ use http_body_util::combinators::UnsyncBoxBody;
 use hyper::body::Bytes;
 #[cfg(feature = "dynamic_routing")]
 use hyper_util::client::legacy::connect::HttpConnector;
+#[cfg(feature = "dynamic_routing")]
+use router_env::logger;
 use serde;
 #[cfg(feature = "dynamic_routing")]
 use tonic::Status;
@@ -76,3 +80,51 @@ impl GrpcClientSettings {
         })
     }
 }
+
+/// Contains grpc headers
+#[derive(Debug)]
+pub struct GrpcHeaders {
+    /// Tenant id
+    pub tenant_id: String,
+    /// Request id
+    pub request_id: Option<String>,
+}
+
+#[cfg(feature = "dynamic_routing")]
+/// Trait to add necessary headers to the tonic Request
+pub(crate) trait AddHeaders {
+    /// Add necessary header fields to the tonic Request
+    fn add_headers_to_grpc_request(&mut self, headers: GrpcHeaders);
+}
+
+#[cfg(feature = "dynamic_routing")]
+impl<T> AddHeaders for tonic::Request<T> {
+    #[track_caller]
+    fn add_headers_to_grpc_request(&mut self, headers: GrpcHeaders) {
+        headers.tenant_id
+            .parse()
+            .map(|tenant_id| {
+                self
+                    .metadata_mut()
+                    .append(consts::TENANT_HEADER, tenant_id)
+            })
+            .inspect_err(
+                |err| logger::warn!(header_parse_error=?err,"invalid {} received",consts::TENANT_HEADER),
+            )
+            .ok();
+
+        headers.request_id.map(|request_id| {
+            request_id
+                .parse()
+                .map(|request_id| {
+                    self
+                        .metadata_mut()
+                        .append(consts::X_REQUEST_ID, request_id)
+                })
+                .inspect_err(
+                    |err| logger::warn!(header_parse_error=?err,"invalid {} received",consts::X_REQUEST_ID),
+                )
+                .ok();
+        });
+    }
+}
diff --git a/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs b/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs
index 6587b7941f4..bc5ce499727 100644
--- a/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs
+++ b/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs
@@ -21,6 +21,7 @@ pub mod elimination_rate {
 }
 
 use super::{Client, DynamicRoutingError, DynamicRoutingResult};
+use crate::grpc_client::{AddHeaders, GrpcHeaders};
 
 /// The trait Elimination Based Routing would have the functions required to support performance, calculation and invalidation bucket
 #[async_trait::async_trait]
@@ -32,6 +33,7 @@ pub trait EliminationBasedRouting: dyn_clone::DynClone + Send + Sync {
         params: String,
         labels: Vec<RoutableConnectorChoice>,
         configs: Option<EliminationConfig>,
+        headers: GrpcHeaders,
     ) -> DynamicRoutingResult<EliminationResponse>;
     /// To update the bucket size and ttl for list of connectors with its respective bucket name
     async fn update_elimination_bucket_config(
@@ -40,11 +42,13 @@ pub trait EliminationBasedRouting: dyn_clone::DynClone + Send + Sync {
         params: String,
         report: Vec<RoutableConnectorChoiceWithBucketName>,
         config: Option<EliminationConfig>,
+        headers: GrpcHeaders,
     ) -> DynamicRoutingResult<UpdateEliminationBucketResponse>;
     /// To invalidate the previous id's bucket
     async fn invalidate_elimination_bucket(
         &self,
         id: String,
+        headers: GrpcHeaders,
     ) -> DynamicRoutingResult<InvalidateBucketResponse>;
 }
 
@@ -56,6 +60,7 @@ impl EliminationBasedRouting for EliminationAnalyserClient<Client> {
         params: String,
         label_input: Vec<RoutableConnectorChoice>,
         configs: Option<EliminationConfig>,
+        headers: GrpcHeaders,
     ) -> DynamicRoutingResult<EliminationResponse> {
         let labels = label_input
             .into_iter()
@@ -64,13 +69,15 @@ impl EliminationBasedRouting for EliminationAnalyserClient<Client> {
 
         let config = configs.map(ForeignTryFrom::foreign_try_from).transpose()?;
 
-        let request = tonic::Request::new(EliminationRequest {
+        let mut request = tonic::Request::new(EliminationRequest {
             id,
             params,
             labels,
             config,
         });
 
+        request.add_headers_to_grpc_request(headers);
+
         let response = self
             .clone()
             .get_elimination_status(request)
@@ -89,6 +96,7 @@ impl EliminationBasedRouting for EliminationAnalyserClient<Client> {
         params: String,
         report: Vec<RoutableConnectorChoiceWithBucketName>,
         configs: Option<EliminationConfig>,
+        headers: GrpcHeaders,
     ) -> DynamicRoutingResult<UpdateEliminationBucketResponse> {
         let config = configs.map(ForeignTryFrom::foreign_try_from).transpose()?;
 
@@ -102,13 +110,15 @@ impl EliminationBasedRouting for EliminationAnalyserClient<Client> {
             })
             .collect::<Vec<_>>();
 
-        let request = tonic::Request::new(UpdateEliminationBucketRequest {
+        let mut request = tonic::Request::new(UpdateEliminationBucketRequest {
             id,
             params,
             labels_with_bucket_name,
             config,
         });
 
+        request.add_headers_to_grpc_request(headers);
+
         let response = self
             .clone()
             .update_elimination_bucket(request)
@@ -122,8 +132,11 @@ impl EliminationBasedRouting for EliminationAnalyserClient<Client> {
     async fn invalidate_elimination_bucket(
         &self,
         id: String,
+        headers: GrpcHeaders,
     ) -> DynamicRoutingResult<InvalidateBucketResponse> {
-        let request = tonic::Request::new(InvalidateBucketRequest { id });
+        let mut request = tonic::Request::new(InvalidateBucketRequest { id });
+
+        request.add_headers_to_grpc_request(headers);
 
         let response = self
             .clone()
diff --git a/crates/external_services/src/grpc_client/dynamic_routing/success_rate_client.rs b/crates/external_services/src/grpc_client/dynamic_routing/success_rate_client.rs
index f6d3efb8876..3cf06ab63be 100644
--- a/crates/external_services/src/grpc_client/dynamic_routing/success_rate_client.rs
+++ b/crates/external_services/src/grpc_client/dynamic_routing/success_rate_client.rs
@@ -21,6 +21,7 @@ pub mod success_rate {
     tonic::include_proto!("success_rate");
 }
 use super::{Client, DynamicRoutingError, DynamicRoutingResult};
+use crate::grpc_client::{AddHeaders, GrpcHeaders};
 /// The trait Success Based Dynamic Routing would have the functions required to support the calculation and updation window
 #[async_trait::async_trait]
 pub trait SuccessBasedDynamicRouting: dyn_clone::DynClone + Send + Sync {
@@ -31,6 +32,7 @@ pub trait SuccessBasedDynamicRouting: dyn_clone::DynClone + Send + Sync {
         success_rate_based_config: SuccessBasedRoutingConfig,
         params: String,
         label_input: Vec<RoutableConnectorChoice>,
+        headers: GrpcHeaders,
     ) -> DynamicRoutingResult<CalSuccessRateResponse>;
     /// To update the success rate with the given label
     async fn update_success_rate(
@@ -39,11 +41,13 @@ pub trait SuccessBasedDynamicRouting: dyn_clone::DynClone + Send + Sync {
         success_rate_based_config: SuccessBasedRoutingConfig,
         params: String,
         response: Vec<RoutableConnectorChoiceWithStatus>,
+        headers: GrpcHeaders,
     ) -> DynamicRoutingResult<UpdateSuccessRateWindowResponse>;
     /// To invalidates the success rate routing keys
     async fn invalidate_success_rate_routing_keys(
         &self,
         id: String,
+        headers: GrpcHeaders,
     ) -> DynamicRoutingResult<InvalidateWindowsResponse>;
 }
 
@@ -55,6 +59,7 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient<Client> {
         success_rate_based_config: SuccessBasedRoutingConfig,
         params: String,
         label_input: Vec<RoutableConnectorChoice>,
+        headers: GrpcHeaders,
     ) -> DynamicRoutingResult<CalSuccessRateResponse> {
         let labels = label_input
             .into_iter()
@@ -66,13 +71,15 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient<Client> {
             .map(ForeignTryFrom::foreign_try_from)
             .transpose()?;
 
-        let request = tonic::Request::new(CalSuccessRateRequest {
+        let mut request = tonic::Request::new(CalSuccessRateRequest {
             id,
             params,
             labels,
             config,
         });
 
+        request.add_headers_to_grpc_request(headers);
+
         let response = self
             .clone()
             .fetch_success_rate(request)
@@ -91,6 +98,7 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient<Client> {
         success_rate_based_config: SuccessBasedRoutingConfig,
         params: String,
         label_input: Vec<RoutableConnectorChoiceWithStatus>,
+        headers: GrpcHeaders,
     ) -> DynamicRoutingResult<UpdateSuccessRateWindowResponse> {
         let config = success_rate_based_config
             .config
@@ -105,13 +113,15 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient<Client> {
             })
             .collect();
 
-        let request = tonic::Request::new(UpdateSuccessRateWindowRequest {
+        let mut request = tonic::Request::new(UpdateSuccessRateWindowRequest {
             id,
             params,
             labels_with_status,
             config,
         });
 
+        request.add_headers_to_grpc_request(headers);
+
         let response = self
             .clone()
             .update_success_rate_window(request)
@@ -126,8 +136,11 @@ impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient<Client> {
     async fn invalidate_success_rate_routing_keys(
         &self,
         id: String,
+        headers: GrpcHeaders,
     ) -> DynamicRoutingResult<InvalidateWindowsResponse> {
-        let request = tonic::Request::new(InvalidateWindowsRequest { id });
+        let mut request = tonic::Request::new(InvalidateWindowsRequest { id });
+
+        request.add_headers_to_grpc_request(headers);
 
         let response = self
             .clone()
diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs
index 746b6b7fb40..6d3c0973283 100644
--- a/crates/router/src/core/payments/routing.rs
+++ b/crates/router/src/core/payments/routing.rs
@@ -1343,6 +1343,7 @@ pub async fn perform_success_based_routing(
                 success_based_routing_configs,
                 success_based_routing_config_params,
                 routable_connectors,
+                state.get_grpc_headers(),
             )
             .await
             .change_context(errors::RoutingError::SuccessRateCalculationError)
diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs
index 36bcb233d02..5fc54fe96e5 100644
--- a/crates/router/src/core/routing.rs
+++ b/crates/router/src/core/routing.rs
@@ -1408,7 +1408,10 @@ pub async fn success_based_routing_update_configs(
         .as_ref()
         .async_map(|sr_client| async {
             sr_client
-                .invalidate_success_rate_routing_keys(prefix_of_dynamic_routing_keys)
+                .invalidate_success_rate_routing_keys(
+                    prefix_of_dynamic_routing_keys,
+                    state.get_grpc_headers(),
+                )
                 .await
                 .change_context(errors::ApiErrorResponse::GenericNotFoundError {
                     message: "Failed to invalidate the routing keys".to_string(),
diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs
index b3c951f0964..3ead2966ed7 100644
--- a/crates/router/src/core/routing/helpers.rs
+++ b/crates/router/src/core/routing/helpers.rs
@@ -719,6 +719,7 @@ pub async fn push_metrics_with_update_window_for_success_based_routing(
                 success_based_routing_configs.clone(),
                 success_based_routing_config_params.clone(),
                 routable_connectors.clone(),
+                state.get_grpc_headers(),
             )
             .await
             .change_context(errors::ApiErrorResponse::InternalServerError)
@@ -854,6 +855,7 @@ pub async fn push_metrics_with_update_window_for_success_based_routing(
                     },
                     payment_status_attribute == common_enums::AttemptStatus::Charged,
                 )],
+                state.get_grpc_headers(),
             )
             .await
             .change_context(errors::ApiErrorResponse::InternalServerError)
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 766679491c5..7daed86079f 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -12,7 +12,10 @@ use common_utils::id_type;
 use external_services::email::{
     no_email::NoEmailClient, ses::AwsSes, smtp::SmtpServer, EmailClientConfigs, EmailService,
 };
-use external_services::{file_storage::FileStorageInterface, grpc_client::GrpcClients};
+use external_services::{
+    file_storage::FileStorageInterface,
+    grpc_client::{GrpcClients, GrpcHeaders},
+};
 use hyperswitch_interfaces::{
     encryption_interface::EncryptionManagementInterface,
     secrets_interface::secret_state::{RawSecret, SecuredSecret},
@@ -123,6 +126,12 @@ impl SessionState {
             event_context: events::EventContext::new(self.event_handler.clone()),
         }
     }
+    pub fn get_grpc_headers(&self) -> GrpcHeaders {
+        GrpcHeaders {
+            tenant_id: self.tenant.tenant_id.get_string_repr().to_string(),
+            request_id: self.request_id.map(|req_id| (*req_id).to_string()),
+        }
+    }
 }
 
 pub trait SessionStateInfo {

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [ ] Enhancement
- [x] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
This PR adds support for sending the `x-tenant-id` and `x-request-id` in the grpc headers

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->


## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
1. Create merchant_account, api_key and mca (any connector)
2. Toggle dynamic routing

```
curl --location --request POST 'http://localhost:8080/account/merchant_1734609410/business_profile/pro_gPTjn9jLm0CxI8ZD1omL/dynamic_routing/success_based/toggle?enable=metrics' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_K70Wr6zqJLbPECg8eEXrnZ43TW41JNUuXYHzRB50geiG0lViLExjxNUld6sgdNd0' \
--data ''
```

3. Create a payment

```
curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_K70Wr6zqJLbPECg8eEXrnZ43TW41JNUuXYHzRB50geiG0lViLExjxNUld6sgdNd0' \
--data '{
    "amount": 6540,
    "authentication_type": "no_three_ds",
    "confirm": true,
    "currency": "USD",
    "customer_acceptance": {
        "acceptance_type": "online"
    },
    "customer_id": "cus_uYakn3OQTUtAgetLDOE1",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "card_cvc": "123",
            "card_exp_month": "10",
            "card_exp_year": "25",
            "card_holder_name": "joseph Doe",
            "card_number": "4242424242424242"
        }
    }
}'
```

Check the grafana dashboard with `app = dynamo` label in loki. Logs should have `x-tenant-id` and `x-request-id` in the headers.

## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
