Skip to content

Commit 82d2cc1

Browse files
LeoPatOZ0xNeshi
andauthored
Fix Fee history test (#66)
Resolves #59 Adds safe anvil drop (thread blocking drop of anvil) --------- Co-authored-by: Nenad <nenad.misic@openzeppelin.com>
1 parent e5c3e2a commit 82d2cc1

6 files changed

Lines changed: 75 additions & 47 deletions

File tree

tests/common/mod.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,32 @@ use alloy::{
1010
providers::{Provider, ProviderBuilder, RootProvider, ext::AnvilApi},
1111
};
1212
use robust_provider::{RobustProvider, RobustProviderBuilder};
13+
use tokio::time::sleep;
14+
15+
/// Safely drops an `AnvilInstance` by sending SIGTERM and waiting for the process to exit.
16+
/// This ensures the anvil process is fully terminated before returning.
17+
pub fn safe_drop_anvil(mut anvil: AnvilInstance) {
18+
let child = anvil.child_mut();
19+
#[cfg(unix)]
20+
{
21+
use std::process::Command;
22+
23+
if let Ok(out) = Command::new("kill").arg("-SIGTERM").arg(child.id().to_string()).output() &&
24+
out.status.success()
25+
{
26+
let _ = child.wait();
27+
return;
28+
}
29+
}
30+
31+
if let Err(err) = child.kill() {
32+
eprintln!("alloy-node-bindings: failed to kill anvil process: {err}");
33+
} else {
34+
let _ = child.wait();
35+
}
36+
37+
std::mem::forget(anvil);
38+
}
1339

1440
alloy::sol! {
1541
// Built directly with solc 0.8.30+commit.73712a01.Darwin.appleclang
@@ -66,6 +92,10 @@ pub async fn setup_anvil_with_blocks(
6692
) -> anyhow::Result<(AnvilInstance, RobustProvider, impl Provider)> {
6793
let (anvil, robust, alloy_provider) = setup_anvil().await?;
6894
alloy_provider.anvil_mine(Some(num_blocks), None).await?;
95+
// some tests rely on blocks being cached, this allows time for blocks to be propelry cached in
96+
// foundry.
97+
// for more info, see: https://github.com/OpenZeppelin/Robust-Provider/issues/59#issuecomment-4010684560
98+
sleep(Duration::from_millis(200)).await;
6999
Ok((anvil, robust, alloy_provider))
70100
}
71101

tests/eth_namespace/fee.rs

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use alloy::{
99
// ============================================================================
1010

1111
#[tokio::test]
12-
#[ignore = "Flaky, see: https://github.com/OpenZeppelin/Robust-Provider/issues/59"]
1312
async fn test_get_blob_base_fee_succeeds() -> anyhow::Result<()> {
1413
let (_anvil, robust, alloy_provider) = setup_anvil().await?;
1514

@@ -26,11 +25,10 @@ async fn test_get_blob_base_fee_succeeds() -> anyhow::Result<()> {
2625
// ============================================================================
2726

2827
#[tokio::test]
29-
#[ignore = "Flaky, see: https://github.com/OpenZeppelin/Robust-Provider/issues/59"]
3028
async fn test_get_fee_history_succeeds() -> anyhow::Result<()> {
31-
let (_anvil, robust, alloy_provider) = setup_anvil_with_blocks(100).await?;
29+
let (_anvil, robust, alloy_provider) = setup_anvil_with_blocks(1).await?;
3230

33-
let block_count = 10;
31+
let block_count = 1;
3432
let reward_percentiles = [25.0, 50.0, 75.0];
3533

3634
let robust_fee_history =
@@ -49,7 +47,6 @@ async fn test_get_fee_history_succeeds() -> anyhow::Result<()> {
4947
// ============================================================================
5048

5149
#[tokio::test]
52-
#[ignore = "Flaky, see: https://github.com/OpenZeppelin/Robust-Provider/issues/59"]
5350
async fn test_get_gas_price_succeeds() -> anyhow::Result<()> {
5451
let (_anvil, robust, alloy_provider) = setup_anvil().await?;
5552

@@ -66,7 +63,6 @@ async fn test_get_gas_price_succeeds() -> anyhow::Result<()> {
6663
// ============================================================================
6764

6865
#[tokio::test]
69-
#[ignore = "Flaky, see: https://github.com/OpenZeppelin/Robust-Provider/issues/59"]
7066
async fn test_get_max_priority_fee_per_gas_succeeds() -> anyhow::Result<()> {
7167
let (_anvil, robust, alloy_provider) = setup_anvil().await?;
7268

@@ -83,7 +79,6 @@ async fn test_get_max_priority_fee_per_gas_succeeds() -> anyhow::Result<()> {
8379
// ============================================================================
8480

8581
#[tokio::test]
86-
#[ignore = "Flaky, see: https://github.com/OpenZeppelin/Robust-Provider/issues/59"]
8782
async fn test_estimate_eip1559_fees_succeeds() -> anyhow::Result<()> {
8883
let (_anvil, robust, alloy_provider) = setup_anvil().await?;
8984

@@ -100,9 +95,8 @@ async fn test_estimate_eip1559_fees_succeeds() -> anyhow::Result<()> {
10095
// ============================================================================
10196

10297
#[tokio::test]
103-
#[ignore = "Flaky, see: https://github.com/OpenZeppelin/Robust-Provider/issues/59"]
10498
async fn test_estimate_eip1559_fees_with_default_estimator() -> anyhow::Result<()> {
105-
let (_anvil, robust, alloy_provider) = setup_anvil_with_blocks(100).await?;
99+
let (_anvil, robust, alloy_provider) = setup_anvil().await?;
106100

107101
let robust_gas = robust.estimate_eip1559_fees_with(Eip1559Estimator::default()).await?;
108102
let alloy_gas = alloy_provider.estimate_eip1559_fees_with(Eip1559Estimator::default()).await?;
@@ -113,9 +107,8 @@ async fn test_estimate_eip1559_fees_with_default_estimator() -> anyhow::Result<(
113107
}
114108

115109
#[tokio::test]
116-
#[ignore = "Flaky, see: https://github.com/OpenZeppelin/Robust-Provider/issues/59"]
117110
async fn test_estimate_eip1559_fees_with_custom_estimator() -> anyhow::Result<()> {
118-
let (_anvil, robust, alloy_provider) = setup_anvil_with_blocks(100).await?;
111+
let (_anvil, robust, alloy_provider) = setup_anvil().await?;
119112

120113
let robust_gas = robust
121114
.estimate_eip1559_fees_with(Eip1559Estimator::new(|base_fee, _rewards| Eip1559Estimation {
@@ -137,9 +130,8 @@ async fn test_estimate_eip1559_fees_with_custom_estimator() -> anyhow::Result<()
137130
}
138131

139132
#[tokio::test]
140-
#[ignore = "Flaky, see: https://github.com/OpenZeppelin/Robust-Provider/issues/59"]
141133
async fn test_estimate_eip1559_fees_with_zero_priority_fee() -> anyhow::Result<()> {
142-
let (_anvil, robust, alloy_provider) = setup_anvil_with_blocks(50).await?;
134+
let (_anvil, robust, alloy_provider) = setup_anvil().await?;
143135

144136
let robust_gas = robust
145137
.estimate_eip1559_fees_with(Eip1559Estimator::new(|base_fee, _rewards| Eip1559Estimation {
@@ -161,9 +153,8 @@ async fn test_estimate_eip1559_fees_with_zero_priority_fee() -> anyhow::Result<(
161153
}
162154

163155
#[tokio::test]
164-
#[ignore = "Flaky, see: https://github.com/OpenZeppelin/Robust-Provider/issues/59"]
165156
async fn test_estimate_eip1559_fees_with_high_priority_fee() -> anyhow::Result<()> {
166-
let (_anvil, robust, alloy_provider) = setup_anvil_with_blocks(50).await?;
157+
let (_anvil, robust, alloy_provider) = setup_anvil().await?;
167158

168159
let robust_gas = robust
169160
.estimate_eip1559_fees_with(Eip1559Estimator::new(|base_fee, _rewards| Eip1559Estimation {
@@ -185,9 +176,8 @@ async fn test_estimate_eip1559_fees_with_high_priority_fee() -> anyhow::Result<(
185176
}
186177

187178
#[tokio::test]
188-
#[ignore = "Flaky, see: https://github.com/OpenZeppelin/Robust-Provider/issues/59"]
189179
async fn test_estimate_eip1559_fees_with_reward_percentile_based() -> anyhow::Result<()> {
190-
let (_anvil, robust, alloy_provider) = setup_anvil_with_blocks(100).await?;
180+
let (_anvil, robust, alloy_provider) = setup_anvil().await?;
191181

192182
let robust_gas = robust
193183
.estimate_eip1559_fees_with(Eip1559Estimator::new(|base_fee, rewards| Eip1559Estimation {

tests/http_subscription.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ use common::{BUFFER_TIME, SHORT_TIMEOUT};
1818
use robust_provider::{Error, RobustProviderBuilder};
1919
use tokio_stream::StreamExt;
2020

21+
use crate::common::safe_drop_anvil;
22+
2123
// ============================================================================
2224
// Test Helpers
2325
// ============================================================================
@@ -115,7 +117,7 @@ async fn test_ws_only_chain_works_with_http_subscriptions_enabled() -> anyhow::R
115117
assert!(subscription.is_empty());
116118

117119
// Kill WS primary and ensure we can still fail over to WS fallback.
118-
drop(anvil_primary);
120+
safe_drop_anvil(anvil_primary);
119121

120122
tokio::spawn(async move {
121123
// sleep just enough before mining to ensure subscription switches to this fallback provider
@@ -166,7 +168,7 @@ async fn test_mixed_fallback_ordering_ws_to_http_to_ws() -> anyhow::Result<()> {
166168
assert_eq!(block.number, 1);
167169

168170
// Kill WS primary to force failover to HTTP fallback.
169-
drop(anvil_ws_primary);
171+
safe_drop_anvil(anvil_ws_primary);
170172
let http_clone = http_fallback.clone();
171173
let http_mining_task = tokio::spawn(async move {
172174
tokio::time::sleep(BUFFER_TIME).await;
@@ -202,7 +204,7 @@ async fn test_mixed_fallback_ordering_ws_to_http_to_ws() -> anyhow::Result<()> {
202204
}
203205

204206
// Now kill HTTP fallback too, and ensure we can fail over to WS fallback.
205-
drop(anvil_http);
207+
safe_drop_anvil(anvil_http);
206208
let ws2_clone = ws_fallback.clone();
207209
tokio::spawn(async move {
208210
// Wait long enough for:
@@ -328,7 +330,7 @@ async fn test_failover_ws_to_http_on_provider_death() -> anyhow::Result<()> {
328330
assert!(subscription.is_empty());
329331

330332
// Kill WS provider - this will cause subscription to fail
331-
drop(anvil_ws);
333+
safe_drop_anvil(anvil_ws);
332334

333335
// Spawn task to mine repeatedly on HTTP after timeout triggers failover.
334336
// Mining just once can be flaky if it happens before the HTTP poller is fully established.
@@ -386,7 +388,7 @@ async fn test_failover_http_to_ws_on_provider_death() -> anyhow::Result<()> {
386388
assert_eq!(block.number, 1, "Should start on HTTP primary");
387389

388390
// Kill HTTP provider
389-
drop(anvil_http);
391+
safe_drop_anvil(anvil_http);
390392

391393
// Mine on WS shortly after HTTP error is detected.
392394
// The HTTP poll will fail quickly (connection refused), triggering immediate failover to WS.
@@ -590,7 +592,7 @@ async fn test_all_providers_fail_returns_error() -> anyhow::Result<()> {
590592
.expect("recv error");
591593

592594
// Kill the only provider
593-
drop(anvil);
595+
safe_drop_anvil(anvil);
594596

595597
// Next recv should eventually error (after timeout)
596598
let result = tokio::time::timeout(Duration::from_secs(5), subscription.recv()).await;
@@ -693,7 +695,7 @@ async fn test_poll_interval_propagated_from_builder() -> anyhow::Result<()> {
693695
assert!(subscription.is_empty());
694696

695697
// Kill WS to force failover to HTTP
696-
drop(anvil_ws);
698+
safe_drop_anvil(anvil_ws);
697699

698700
// Mine on HTTP and wait for failover
699701
let http_clone = http_provider.clone();
@@ -774,7 +776,7 @@ async fn test_http_reconnect_validates_provider() -> anyhow::Result<()> {
774776
assert_eq!(block.number, 1);
775777

776778
// Kill primary - subscription should failover to fallback
777-
drop(anvil_primary);
779+
safe_drop_anvil(anvil_primary);
778780

779781
// Trigger failover by waiting for timeout, then mine on fallback
780782
let fb_clone = fallback.clone();
@@ -844,8 +846,8 @@ async fn test_timeout_triggered_failover_with_multiple_fallbacks() -> anyhow::Re
844846
assert_eq!(block.number, 1);
845847

846848
// Kill primary AND fallback1 - only fallback2 will work
847-
drop(anvil_primary);
848-
drop(anvil_fb1);
849+
safe_drop_anvil(anvil_primary);
850+
safe_drop_anvil(anvil_fb1);
849851

850852
// Don't mine on fallback2 immediately - let timeouts trigger failover
851853
// After SHORT_TIMEOUT, primary poll fails -> try fallback1
@@ -895,8 +897,8 @@ async fn test_single_fallback_timeout_exhausts_providers() -> anyhow::Result<()>
895897
assert_eq!(block.number, 1);
896898

897899
// Kill both providers
898-
drop(anvil_primary);
899-
drop(anvil_fb);
900+
safe_drop_anvil(anvil_primary);
901+
safe_drop_anvil(anvil_fb);
900902

901903
// Don't mine anything - let it timeout and exhaust providers
902904
let result = tokio::time::timeout(Duration::from_secs(3), subscription.recv()).await;

tests/rpc_failover.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use alloy::{
1212
};
1313
use robust_provider::{Error, RobustProviderBuilder};
1414

15+
use crate::common::safe_drop_anvil;
16+
1517
// ============================================================================
1618
// RPC Failover Tests
1719
// ============================================================================
@@ -39,7 +41,7 @@ async fn test_rpc_failover_when_primary_dead() -> anyhow::Result<()> {
3941
assert_eq!(block_num, 10);
4042

4143
// Kill primary
42-
drop(anvil_primary);
44+
safe_drop_anvil(anvil_primary);
4345

4446
// Should failover to fallback
4547
let block_num = robust.get_block_number().await?;
@@ -71,8 +73,8 @@ async fn test_rpc_cycles_through_multiple_fallbacks() -> anyhow::Result<()> {
7173
.await?;
7274

7375
// Kill primary and first fallback
74-
drop(anvil_primary);
75-
drop(anvil_fb1);
76+
safe_drop_anvil(anvil_primary);
77+
safe_drop_anvil(anvil_fb1);
7678

7779
// give some time so that OS can actually finish killing Anvil processes
7880
tokio::time::sleep(Duration::from_millis(100)).await;
@@ -99,8 +101,8 @@ async fn test_rpc_all_providers_fail() -> anyhow::Result<()> {
99101
.await?;
100102

101103
// Kill all providers
102-
drop(anvil_primary);
103-
drop(anvil_fallback);
104+
safe_drop_anvil(anvil_primary);
105+
safe_drop_anvil(anvil_fallback);
104106

105107
// Should fail after trying all providers
106108
let result = robust.get_block_number().await;
@@ -148,7 +150,7 @@ async fn test_operation_completes_when_provider_unavailable() -> anyhow::Result<
148150
// Create and immediately kill provider so endpoint doesn't exist
149151
let anvil = Anvil::new().try_spawn()?;
150152
let endpoint = anvil.endpoint_url();
151-
drop(anvil);
153+
safe_drop_anvil(anvil);
152154

153155
let provider = ProviderBuilder::new().connect_http(endpoint);
154156

@@ -190,7 +192,7 @@ async fn test_get_accounts_failover() -> anyhow::Result<()> {
190192
.await?;
191193

192194
// Kill primary
193-
drop(anvil_primary);
195+
safe_drop_anvil(anvil_primary);
194196

195197
let accounts = robust.get_accounts().await?;
196198
assert!(!accounts.is_empty());
@@ -216,7 +218,7 @@ async fn test_get_balance_failover() -> anyhow::Result<()> {
216218
.await?;
217219

218220
// Kill primary
219-
drop(anvil_primary);
221+
safe_drop_anvil(anvil_primary);
220222

221223
let balance = robust.get_balance(address).await?;
222224
assert!(balance > alloy::primitives::U256::ZERO);
@@ -241,7 +243,7 @@ async fn test_get_block_failover() -> anyhow::Result<()> {
241243
.await?;
242244

243245
// Kill primary
244-
drop(anvil_primary);
246+
safe_drop_anvil(anvil_primary);
245247

246248
let block = robust.get_block_by_number(BlockNumberOrTag::Number(3)).await?;
247249
assert_eq!(block.header.number, 3);

tests/subscription.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ use robust_provider::{
1919
use tokio::time::sleep;
2020
use tokio_stream::StreamExt;
2121

22+
use crate::common::safe_drop_anvil;
23+
2224
// ============================================================================
2325
// Test Helpers
2426
// ============================================================================
@@ -390,7 +392,7 @@ async fn test_single_fallback_provider() -> anyhow::Result<()> {
390392
assert_next_block!(stream, 1);
391393

392394
// Kill primary so reconnect attempts fail
393-
drop(anvil_pp);
395+
safe_drop_anvil(anvil_pp);
394396

395397
// PP -> FB
396398
trigger_failover(&mut stream, fallback.clone(), 1).await?;
@@ -423,7 +425,7 @@ async fn subscription_cycles_through_multiple_fallbacks() -> anyhow::Result<()>
423425
assert_next_block!(stream, 1);
424426

425427
// Kill primary - all future PP reconnection attempts will fail
426-
drop(anvil_pp);
428+
safe_drop_anvil(anvil_pp);
427429

428430
// PP times out -> FP1
429431
trigger_failover(&mut stream, fb_1.clone(), 1).await?;
@@ -468,7 +470,7 @@ async fn test_many_fallback_providers() -> anyhow::Result<()> {
468470
assert_next_block!(stream, 1);
469471

470472
// Kill primary
471-
drop(anvil_pp);
473+
safe_drop_anvil(anvil_pp);
472474

473475
// Cycle through all fallbacks
474476
trigger_failover(&mut stream, fb_1.clone(), 1).await?;
@@ -668,7 +670,7 @@ async fn test_multiple_failed_reconnection_attempts() -> anyhow::Result<()> {
668670
assert_next_block!(stream, 1);
669671

670672
// Kill primary
671-
drop(anvil_pp);
673+
safe_drop_anvil(anvil_pp);
672674

673675
// Failover to fb_1 (primary is dead)
674676
trigger_failover(&mut stream, fb_1.clone(), 1).await?;
@@ -739,7 +741,7 @@ async fn test_backend_gone_error_propagation() -> anyhow::Result<()> {
739741
assert_next_block!(stream, 1);
740742

741743
// Kill the provider
742-
drop(anvil);
744+
safe_drop_anvil(anvil);
743745

744746
// Should get BackendGone or Timeout error
745747
assert!(matches!(stream.next().await.unwrap(), Err(Error::Timeout)));
@@ -764,7 +766,7 @@ async fn test_immediate_consecutive_failures() -> anyhow::Result<()> {
764766
assert_next_block!(stream, 1);
765767

766768
// Kill provider immediately
767-
drop(anvil);
769+
safe_drop_anvil(anvil);
768770

769771
// First failure
770772
assert!(matches!(stream.next().await.unwrap(), Err(Error::Timeout)));

0 commit comments

Comments
 (0)