Skip to content

Commit 04aeaeb

Browse files
author
Gilad Chase
committed
chore(apollo_l1_endpoint_monitor): add integration test
1 parent e6ff66d commit 04aeaeb

File tree

4 files changed

+74
-2
lines changed

4 files changed

+74
-2
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/apollo_l1_endpoint_monitor/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ tracing.workspace = true
1313
url = { workspace = true, features = ["serde"] }
1414

1515
[dev-dependencies]
16+
alloy = { workspace = true, features = ["node-bindings"] }
1617
mockito.workspace = true
18+
papyrus_base_layer = { workspace = true, features = ["testing"] }
1719
tokio.workspace = true
1820

1921
[lints]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use apollo_l1_endpoint_monitor::monitor::{
2+
L1EndpointMonitor,
3+
L1EndpointMonitorConfig,
4+
L1EndpointMonitorError,
5+
};
6+
use papyrus_base_layer::test_utils::anvil;
7+
use url::Url;
8+
9+
/// Integration test: two Anvil nodes plus a bogus endpoint to exercise cycling and failure.
10+
#[tokio::test]
11+
async fn end_to_end_cycle_and_recovery() {
12+
// Spin up two ephemeral Anvil nodes.
13+
// IMPORTANT: This is one of the only cases where two anvil nodes are needed simultaneously,
14+
// since we are flow testing two separate L1 nodes. Other tests should never use more than one
15+
// at a time!
16+
let good_node_1 = anvil(None);
17+
let good_url_1 = good_node_1.endpoint_url();
18+
let good_node_2 = anvil(None);
19+
let good_url_2 = good_node_2.endpoint_url();
20+
21+
// Bogus endpoint on port 1 that is likely to be unbound, see the unit tests for more details.
22+
let bad_node_url = Url::parse("http://localhost:1").unwrap();
23+
24+
// Initialize monitor starting at the bad index.
25+
let mut monitor = L1EndpointMonitor {
26+
current_l1_endpoint_index: 0,
27+
config: L1EndpointMonitorConfig {
28+
ordered_l1_endpoint_urls: vec![
29+
bad_node_url.clone(),
30+
good_url_1.clone(),
31+
good_url_2.clone(),
32+
],
33+
},
34+
};
35+
36+
// 1) First call: skip bad and take the first good one.
37+
let active1 = monitor.get_active_l1_endpoint().await.unwrap();
38+
assert_eq!(active1, good_url_1);
39+
assert_eq!(monitor.current_l1_endpoint_index, 1);
40+
41+
// 2) Anvil 1 is going down.
42+
drop(good_node_1);
43+
44+
// Next call: now the first good node is down, switch to second good node.
45+
let active2 = monitor.get_active_l1_endpoint().await.unwrap();
46+
assert_eq!(active2, good_url_2);
47+
assert_eq!(monitor.current_l1_endpoint_index, 2);
48+
49+
// 3) Anvil 2 is now also down!
50+
drop(good_node_2);
51+
52+
// All endpoints are now down --> error. Do this twice for idempotency.
53+
for _ in 0..2 {
54+
let result = monitor.get_active_l1_endpoint().await;
55+
assert_eq!(result, Err(L1EndpointMonitorError::NoActiveL1Endpoint));
56+
assert_eq!(monitor.current_l1_endpoint_index, 2);
57+
}
58+
59+
// ANVIL node 1 has risen!
60+
let good_node_1 = anvil(None);
61+
// Anvil is configured to use an ephemeral port, so this new node will be bound to a fresh port.
62+
// We cannot reuse the previous URL since the old port may no longer be available.
63+
let good_url_1 = good_node_1.endpoint_url();
64+
monitor.config.ordered_l1_endpoint_urls[1] = good_url_1.clone();
65+
// Index wraps around from 2 to 0, 0 is still down so 1 is picked, which is operational now.
66+
let active3 = monitor.get_active_l1_endpoint().await.unwrap();
67+
assert_eq!(active3, good_url_1);
68+
assert_eq!(monitor.current_l1_endpoint_index, 1);
69+
}

crates/papyrus_base_layer/src/test_utils.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ pub fn get_test_ethereum_node() -> (TestEthereumNodeHandle, EthereumContractAddr
100100

101101
// TODO(Arni): Make port non-optional.
102102
// Spin up Anvil instance, a local Ethereum node, dies when dropped.
103-
fn anvil(port: Option<u16>) -> AnvilInstance {
103+
pub fn anvil(port: Option<u16>) -> AnvilInstance {
104104
let mut anvil = Anvil::new();
105-
// If the port is not set explicitly, a random value will be used.
105+
// If the port is not set explicitly, a random ephemeral port is bound and used.
106106
if let Some(port) = port {
107107
anvil = anvil.port(port);
108108
}

0 commit comments

Comments
 (0)