mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-10 12:11:59 +00:00
Prevent port re-use in HTTP API tests (#4745)
## Issue Addressed
CI is plagued by `AddrAlreadyInUse` failures, which are caused by race conditions in allocating free ports.
This PR removes all usages of the `unused_port` crate for Lighthouse's HTTP API, in favour of passing `:0` as the listen address. As a result, the listen address isn't known ahead of time and must be read from the listening socket after it binds. This requires tying some self-referential knots, which is a little disruptive, but hopefully doesn't clash too much with Deneb 🤞
There are still a few usages of `unused_tcp4_port` left in cases where we start external processes, like the `watch` Postgres DB, Anvil, Geth, Nethermind, etc. Removing these usages is non-trivial because it's hard to read the port back from an external process after starting it with `--port 0`. We might be able to do something on Linux where we read from `/proc/`, but I'll leave that for future work.
This commit is contained in:
@@ -65,7 +65,6 @@ sensitive_url = { path = "../../common/sensitive_url" }
|
||||
superstruct = "0.5.0"
|
||||
hex = "0.4.2"
|
||||
exit-future = "0.2.0"
|
||||
unused_port = {path = "../../common/unused_port"}
|
||||
oneshot_broadcast = { path = "../../common/oneshot_broadcast" }
|
||||
|
||||
[[test]]
|
||||
|
||||
@@ -17,8 +17,8 @@ use bls::get_withdrawal_credentials;
|
||||
use execution_layer::{
|
||||
auth::JwtKey,
|
||||
test_utils::{
|
||||
ExecutionBlockGenerator, MockExecutionLayer, TestingBuilder, DEFAULT_JWT_SECRET,
|
||||
DEFAULT_TERMINAL_BLOCK,
|
||||
ExecutionBlockGenerator, MockBuilder, MockBuilderServer, MockExecutionLayer,
|
||||
DEFAULT_JWT_SECRET, DEFAULT_TERMINAL_BLOCK,
|
||||
},
|
||||
ExecutionLayer,
|
||||
};
|
||||
@@ -167,7 +167,6 @@ pub struct Builder<T: BeaconChainTypes> {
|
||||
store_mutator: Option<BoxedMutator<T::EthSpec, T::HotStore, T::ColdStore>>,
|
||||
execution_layer: Option<ExecutionLayer<T::EthSpec>>,
|
||||
mock_execution_layer: Option<MockExecutionLayer<T::EthSpec>>,
|
||||
mock_builder: Option<TestingBuilder<T::EthSpec>>,
|
||||
testing_slot_clock: Option<TestingSlotClock>,
|
||||
runtime: TestRuntime,
|
||||
log: Logger,
|
||||
@@ -301,7 +300,6 @@ where
|
||||
store_mutator: None,
|
||||
execution_layer: None,
|
||||
mock_execution_layer: None,
|
||||
mock_builder: None,
|
||||
testing_slot_clock: None,
|
||||
runtime,
|
||||
log,
|
||||
@@ -433,7 +431,11 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
pub fn mock_execution_layer(mut self) -> Self {
|
||||
pub fn mock_execution_layer(self) -> Self {
|
||||
self.mock_execution_layer_with_config(None)
|
||||
}
|
||||
|
||||
pub fn mock_execution_layer_with_config(mut self, builder_threshold: Option<u128>) -> Self {
|
||||
let spec = self.spec.clone().expect("cannot build without spec");
|
||||
let shanghai_time = spec.capella_fork_epoch.map(|epoch| {
|
||||
HARNESS_GENESIS_TIME + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64()
|
||||
@@ -442,55 +444,15 @@ where
|
||||
self.runtime.task_executor.clone(),
|
||||
DEFAULT_TERMINAL_BLOCK,
|
||||
shanghai_time,
|
||||
None,
|
||||
builder_threshold,
|
||||
Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()),
|
||||
spec,
|
||||
None,
|
||||
);
|
||||
self.execution_layer = Some(mock.el.clone());
|
||||
self.mock_execution_layer = Some(mock);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn mock_execution_layer_with_builder(
|
||||
mut self,
|
||||
beacon_url: SensitiveUrl,
|
||||
builder_threshold: Option<u128>,
|
||||
) -> Self {
|
||||
// Get a random unused port
|
||||
let port = unused_port::unused_tcp4_port().unwrap();
|
||||
let builder_url = SensitiveUrl::parse(format!("http://127.0.0.1:{port}").as_str()).unwrap();
|
||||
|
||||
let spec = self.spec.clone().expect("cannot build without spec");
|
||||
let shanghai_time = spec.capella_fork_epoch.map(|epoch| {
|
||||
HARNESS_GENESIS_TIME + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64()
|
||||
});
|
||||
let mock_el = MockExecutionLayer::new(
|
||||
self.runtime.task_executor.clone(),
|
||||
DEFAULT_TERMINAL_BLOCK,
|
||||
shanghai_time,
|
||||
builder_threshold,
|
||||
Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()),
|
||||
spec.clone(),
|
||||
Some(builder_url.clone()),
|
||||
)
|
||||
.move_to_terminal_block();
|
||||
|
||||
let mock_el_url = SensitiveUrl::parse(mock_el.server.url().as_str()).unwrap();
|
||||
|
||||
self.mock_builder = Some(TestingBuilder::new(
|
||||
mock_el_url,
|
||||
builder_url,
|
||||
beacon_url,
|
||||
spec,
|
||||
self.runtime.task_executor.clone(),
|
||||
));
|
||||
self.execution_layer = Some(mock_el.el.clone());
|
||||
self.mock_execution_layer = Some(mock_el);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Instruct the mock execution engine to always return a "valid" response to any payload it is
|
||||
/// asked to execute.
|
||||
pub fn mock_execution_layer_all_payloads_valid(self) -> Self {
|
||||
@@ -572,7 +534,7 @@ where
|
||||
shutdown_receiver: Arc::new(Mutex::new(shutdown_receiver)),
|
||||
runtime: self.runtime,
|
||||
mock_execution_layer: self.mock_execution_layer,
|
||||
mock_builder: self.mock_builder.map(Arc::new),
|
||||
mock_builder: None,
|
||||
rng: make_rng(),
|
||||
}
|
||||
}
|
||||
@@ -597,7 +559,7 @@ pub struct BeaconChainHarness<T: BeaconChainTypes> {
|
||||
pub runtime: TestRuntime,
|
||||
|
||||
pub mock_execution_layer: Option<MockExecutionLayer<T::EthSpec>>,
|
||||
pub mock_builder: Option<Arc<TestingBuilder<T::EthSpec>>>,
|
||||
pub mock_builder: Option<Arc<MockBuilder<T::EthSpec>>>,
|
||||
|
||||
pub rng: Mutex<StdRng>,
|
||||
}
|
||||
@@ -633,6 +595,49 @@ where
|
||||
.execution_block_generator()
|
||||
}
|
||||
|
||||
pub fn set_mock_builder(&mut self, beacon_url: SensitiveUrl) -> MockBuilderServer {
|
||||
let mock_el = self
|
||||
.mock_execution_layer
|
||||
.as_ref()
|
||||
.expect("harness was not built with mock execution layer");
|
||||
|
||||
let mock_el_url = SensitiveUrl::parse(mock_el.server.url().as_str()).unwrap();
|
||||
|
||||
// Create the builder, listening on a free port.
|
||||
let (mock_builder, mock_builder_server) = MockBuilder::new_for_testing(
|
||||
mock_el_url,
|
||||
beacon_url,
|
||||
self.spec.clone(),
|
||||
self.runtime.task_executor.clone(),
|
||||
);
|
||||
|
||||
// Set the builder URL in the execution layer now that its port is known.
|
||||
let builder_listen_addr = mock_builder_server.local_addr();
|
||||
let port = builder_listen_addr.port();
|
||||
mock_el
|
||||
.el
|
||||
.set_builder_url(
|
||||
SensitiveUrl::parse(format!("http://127.0.0.1:{port}").as_str()).unwrap(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.mock_builder = Some(Arc::new(mock_builder));
|
||||
|
||||
// Sanity check.
|
||||
let el_builder = self
|
||||
.chain
|
||||
.execution_layer
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.builder()
|
||||
.unwrap();
|
||||
let mock_el_builder = mock_el.el.builder().unwrap();
|
||||
assert!(Arc::ptr_eq(&el_builder, &mock_el_builder));
|
||||
|
||||
mock_builder_server
|
||||
}
|
||||
|
||||
pub fn get_all_validators(&self) -> Vec<usize> {
|
||||
(0..self.validator_keypairs.len()).collect()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user