Skip to content

Commit ef99568

Browse files
committed
chore: Resolve conflicts with main
2 parents 407a2bf + 11e11a1 commit ef99568

File tree

10 files changed

+520
-13
lines changed

10 files changed

+520
-13
lines changed

docs/modules/ROOT/pages/error.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,12 @@ a|* `NotFound` - Resource not found errors
160160
* `ExecutionError` - Trigger execution failures
161161
* `ConfigurationError` - Trigger configuration issues
162162
* `Other` - Unclassified errors
163+
164+
|Monitor Executor
165+
|`MonitorExecutionError`
166+
a|* `NotFound` - Resource not found errors
167+
* `ExecutionError` - Monitor execution failures
168+
* `Other` - Unclassified errors
163169
|===
164170

165171
== Error Handling Guidelines

docs/modules/ROOT/pages/rpc.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ This architecture ensures:
153153
3. Both clients maintain efficiency by sharing the same connection pool
154154

155155

156-
*Transport Level*: Each transport client may define its own retry policy:
157-
+
156+
Each transport client may define its own retry policy:
157+
158158
[source,rust]
159159
----
160160

docs/modules/ROOT/pages/structure.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ The main source code directory contains the core implementation files organized
4646
** `expression`: Expression evaluation
4747
** `logging/`: Logging utilities
4848
** `metrics/`: Metrics utilities
49+
** `monitor/`: Monitor configuration test utilities
4950

5051
== Configuration and Data
5152

src/main.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,9 @@ async fn test_monitor_execution(
158158

159159
tracing::info!(
160160
"Network: {}",
161-
details.get("network_slug").unwrap()
161+
details
162+
.get("network_slug")
163+
.unwrap_or(&serde_json::Value::Null)
162164
);
163165

164166
// Get transaction details based on network type
@@ -459,8 +461,11 @@ async fn main() -> Result<()> {
459461
let should_test_monitor_execution = monitor_path.is_some();
460462
// If monitor path is provided, test monitor execution else start the service
461463
if should_test_monitor_execution {
464+
let monitor_path = monitor_path.ok_or(anyhow::anyhow!(
465+
"monitor_path must be defined when testing monitor execution"
466+
))?;
462467
return test_monitor_execution(
463-
monitor_path.unwrap(),
468+
monitor_path,
464469
network_slug,
465470
block_number,
466471
monitor_service,

src/services/blockchain/transports/endpoint_manager.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ impl EndpointManager {
5757
self.client = client;
5858
}
5959

60+
/// Updates the retry policy for the client
61+
///
62+
/// Constructs a new client with the given retry policy and strategy
63+
/// and updates the endpoint manager with the new client
64+
///
65+
/// # Arguments
66+
/// * `retry_policy` - The new retry policy to use for the client
67+
/// * `retry_strategy` - The new retry strategy to use for the client
6068
pub fn set_retry_policy<R: RetryableStrategy + Send + Sync + 'static>(
6169
&mut self,
6270
retry_policy: ExponentialBackoff,

src/services/blockchain/transports/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ pub use endpoint_manager::EndpointManager;
2121
pub use evm::http::EVMTransportClient;
2222
pub use http::HttpTransportClient;
2323
pub use midnight::http::MidnightTransportClient;
24-
use reqwest_middleware::ClientWithMiddleware;
2524
pub use stellar::http::StellarTransportClient;
2625

26+
use reqwest_middleware::ClientWithMiddleware;
2727
use reqwest_retry::{
2828
default_on_request_failure, default_on_request_success, policies::ExponentialBackoff,
2929
Retryable, RetryableStrategy,

tests/integration/blockchain/transports/endpoint_manager.rs

Lines changed: 167 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use mockito::Server;
22
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
3-
use reqwest_retry::policies::ExponentialBackoff;
3+
use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
44
use serde::Serialize;
55
use serde_json::{json, Value};
66
use std::sync::Arc;
@@ -333,3 +333,169 @@ async fn test_rotate_url_connection_failure() {
333333
&vec![invalid_url.to_string()]
334334
);
335335
}
336+
337+
#[tokio::test]
338+
async fn test_update_client() {
339+
let mut server = Server::new_async().await;
340+
341+
// Set up two different responses to differentiate between clients
342+
let initial_mock = server
343+
.mock("POST", "/")
344+
.with_status(200)
345+
.with_header("content-type", "application/json")
346+
.with_body(r#"{"jsonrpc": "2.0", "result": "initial_client", "id": 1}"#)
347+
.expect(1)
348+
.create_async()
349+
.await;
350+
351+
let mut manager =
352+
EndpointManager::new(get_mock_client_builder(), server.url().as_ref(), vec![]);
353+
354+
// Test initial client
355+
let transport = MockTransport::new();
356+
let initial_result = manager
357+
.send_raw_request(&transport, "test_method", Some(json!(["param1"])))
358+
.await
359+
.unwrap();
360+
assert_eq!(initial_result["result"], "initial_client");
361+
initial_mock.assert();
362+
363+
// Set up mock for new client
364+
let updated_mock = server
365+
.mock("POST", "/")
366+
.with_status(200)
367+
.with_header("content-type", "application/json")
368+
.with_body(r#"{"jsonrpc": "2.0", "result": "updated_client", "id": 1}"#)
369+
.expect(1)
370+
.create_async()
371+
.await;
372+
373+
// Create and update to new client with different configuration
374+
let new_client = ClientBuilder::new(reqwest::Client::new())
375+
.with(RetryTransientMiddleware::new_with_policy(
376+
ExponentialBackoff::builder().build_with_max_retries(3),
377+
))
378+
.build();
379+
manager.update_client(new_client);
380+
381+
// Test updated client
382+
let updated_result = manager
383+
.send_raw_request(&transport, "test_method", Some(json!(["param1"])))
384+
.await
385+
.unwrap();
386+
assert_eq!(updated_result["result"], "updated_client");
387+
updated_mock.assert();
388+
}
389+
390+
#[tokio::test]
391+
async fn test_set_retry_policy() {
392+
let mut server = Server::new_async().await;
393+
394+
// Set up a sequence of responses to test retry behavior
395+
let retry_mock = server
396+
.mock("POST", "/")
397+
.with_status(429) // Too Many Requests
398+
.with_body("Rate limited")
399+
.expect(2) // Expect 2 retries
400+
.create_async()
401+
.await;
402+
403+
let success_mock = server
404+
.mock("POST", "/")
405+
.with_status(200)
406+
.with_header("content-type", "application/json")
407+
.with_body(r#"{"jsonrpc": "2.0", "result": "success_after_retry", "id": 1}"#)
408+
.expect(1)
409+
.create_async()
410+
.await;
411+
412+
let mut manager = EndpointManager::new(
413+
get_mock_client_builder(), // Initial client with no retry policy
414+
server.url().as_ref(),
415+
vec![],
416+
);
417+
418+
// Set a custom retry policy with exactly 2 retries
419+
let retry_policy = ExponentialBackoff::builder().build_with_max_retries(2);
420+
421+
manager.set_retry_policy(retry_policy, TransientErrorRetryStrategy);
422+
423+
// Make request that should trigger retries
424+
let transport = MockTransport::new();
425+
let result = manager
426+
.send_raw_request(&transport, "test_method", Some(json!(["param1"])))
427+
.await
428+
.unwrap();
429+
430+
// Verify that we got the successful response after retries
431+
assert_eq!(result["result"], "success_after_retry");
432+
433+
// Verify that both mocks were called the expected number of times
434+
retry_mock.assert();
435+
success_mock.assert();
436+
}
437+
438+
#[tokio::test]
439+
async fn test_send_raw_request_network_error() {
440+
// Set up with an invalid primary URL that will cause a network error
441+
let invalid_url = "http://invalid-domain-that-will-fail:12345";
442+
let mut valid_server = Server::new_async().await;
443+
444+
// Set up mock for fallback server
445+
let success_mock = valid_server
446+
.mock("POST", "/")
447+
.with_status(200)
448+
.with_header("content-type", "application/json")
449+
.with_body(r#"{"jsonrpc": "2.0", "result": "success", "id": 1}"#)
450+
.expect(1)
451+
.create_async()
452+
.await;
453+
454+
let manager = EndpointManager::new(
455+
get_mock_client_builder(),
456+
invalid_url,
457+
vec![valid_server.url()], // Add valid fallback URL
458+
);
459+
let transport = MockTransport::new();
460+
461+
// Send request - should fail first with network error, then rotate and succeed
462+
let result = manager
463+
.send_raw_request(&transport, "test_method", Some(json!(["param1"])))
464+
.await;
465+
466+
// Verify success after rotation
467+
assert!(result.is_ok());
468+
let response = result.unwrap();
469+
assert_eq!(response["result"], "success");
470+
success_mock.assert();
471+
472+
// Verify URL rotation occurred
473+
assert_eq!(&*manager.active_url.read().await, &valid_server.url());
474+
}
475+
476+
#[tokio::test]
477+
async fn test_send_raw_request_network_error_no_fallback() {
478+
// Set up with an invalid URL and no fallbacks
479+
let invalid_url = "http://invalid-domain-that-will-fail:12345";
480+
let manager = EndpointManager::new(
481+
get_mock_client_builder(),
482+
invalid_url,
483+
vec![], // No fallback URLs
484+
);
485+
let transport = MockTransport::new();
486+
487+
// Send request - should fail with network error and no rotation possible
488+
let result = manager
489+
.send_raw_request(&transport, "test_method", Some(json!(["param1"])))
490+
.await;
491+
492+
// Verify error
493+
assert!(result.is_err());
494+
assert!(result
495+
.unwrap_err()
496+
.to_string()
497+
.contains("Failed to send request"));
498+
499+
// Verify URL didn't change
500+
assert_eq!(&*manager.active_url.read().await, invalid_url);
501+
}

tests/integration/blockchain/transports/evm/http.rs

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use mockito::Server;
22
use openzeppelin_monitor::services::blockchain::{
3-
BlockchainTransport, EVMTransportClient, RotatingTransport,
3+
BlockchainTransport, EVMTransportClient, RotatingTransport, TransientErrorRetryStrategy,
44
};
5+
use reqwest_middleware::ClientBuilder;
6+
use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
57
use serde_json::{json, Value};
68

79
use crate::integration::mocks::{
@@ -161,3 +163,108 @@ async fn test_send_raw_request() {
161163
assert_eq!(response["result"]["data"], "success");
162164
no_params_mock.assert();
163165
}
166+
167+
#[tokio::test]
168+
async fn test_set_retry_policy() {
169+
let mut server = Server::new_async().await;
170+
let mock = create_evm_valid_server_mock_network_response(&mut server);
171+
172+
let network = create_evm_test_network_with_urls(vec![&server.url()]);
173+
let mut client = EVMTransportClient::new(&network).await.unwrap();
174+
175+
// Set up a sequence of responses to test retry behavior
176+
let retry_mock = server
177+
.mock("POST", "/")
178+
.with_status(429) // Too Many Requests
179+
.with_body("Rate limited")
180+
.expect(2) // Expect 2 retries
181+
.create_async()
182+
.await;
183+
184+
let success_mock = server
185+
.mock("POST", "/")
186+
.with_status(200)
187+
.with_header("content-type", "application/json")
188+
.with_body(r#"{"jsonrpc": "2.0", "result": "success_after_retry", "id": 1}"#)
189+
.expect(1)
190+
.create_async()
191+
.await;
192+
193+
// Set a custom retry policy with exactly 2 retries
194+
let retry_policy = ExponentialBackoff::builder().build_with_max_retries(2);
195+
let result = client.set_retry_policy(retry_policy, Some(TransientErrorRetryStrategy));
196+
assert!(result.is_ok());
197+
198+
// Make request that should trigger retries
199+
let result = client
200+
.send_raw_request("test_method", Some(json!(["param1"])))
201+
.await;
202+
203+
// Verify success after retries
204+
assert!(result.is_ok());
205+
let response = result.unwrap();
206+
assert_eq!(response["result"], "success_after_retry");
207+
208+
// Verify all mocks were called
209+
mock.assert();
210+
retry_mock.assert();
211+
success_mock.assert();
212+
}
213+
214+
#[tokio::test]
215+
async fn test_update_endpoint_manager_client() {
216+
let mut server = Server::new_async().await;
217+
218+
// Set up initial response
219+
let initial_mock = create_evm_valid_server_mock_network_response(&mut server);
220+
let initial_request_mock = server
221+
.mock("POST", "/")
222+
.with_status(200)
223+
.with_header("content-type", "application/json")
224+
.with_body(r#"{"jsonrpc": "2.0", "result": "initial_client", "id": 1}"#)
225+
.expect(1)
226+
.create_async()
227+
.await;
228+
229+
let network = create_evm_test_network_with_urls(vec![&server.url()]);
230+
let mut client = EVMTransportClient::new(&network).await.unwrap();
231+
232+
// Test initial client
233+
let result = client
234+
.send_raw_request("test_method", Some(json!(["param1"])))
235+
.await
236+
.unwrap();
237+
assert_eq!(result["result"], "initial_client");
238+
239+
// Set up mock for updated client
240+
let updated_mock = server
241+
.mock("POST", "/")
242+
.with_status(200)
243+
.with_header("content-type", "application/json")
244+
.with_body(r#"{"jsonrpc": "2.0", "result": "updated_client", "id": 1}"#)
245+
.expect(1)
246+
.create_async()
247+
.await;
248+
249+
// Create and update to new client
250+
let new_client = ClientBuilder::new(reqwest::Client::new())
251+
.with(RetryTransientMiddleware::new_with_policy(
252+
ExponentialBackoff::builder().build_with_max_retries(3),
253+
))
254+
.build();
255+
256+
let result = client.update_endpoint_manager_client(new_client);
257+
assert!(result.is_ok());
258+
259+
// Test updated client
260+
let result = client
261+
.send_raw_request("test_method", Some(json!(["param1"])))
262+
.await
263+
.unwrap();
264+
assert_eq!(result["result"], "updated_client");
265+
266+
// Verify all mocks were called
267+
initial_mock.assert();
268+
initial_request_mock.assert();
269+
updated_mock.assert();
270+
}

0 commit comments

Comments
 (0)