Skip to content

Commit 17beb18

Browse files
authored
Fix an unbound loop when using a misbehaving relay (#423)
1 parent 653b232 commit 17beb18

3 files changed

Lines changed: 82 additions & 9 deletions

File tree

crates/pbs/src/mev_boost/submit_block.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ async fn submit_block_with_timeout(
9898
let mut remaining_timeout_ms = timeout_ms;
9999
let mut retry = 0;
100100
let mut backoff = Duration::from_millis(250);
101+
let mut request_api_version = api_version;
101102

102103
loop {
103104
let start_request = Instant::now();
@@ -108,12 +109,21 @@ async fn submit_block_with_timeout(
108109
headers.clone(),
109110
remaining_timeout_ms,
110111
retry,
111-
&api_version,
112+
&request_api_version,
112113
fork_name,
113114
)
114115
.await
115116
{
116-
Ok(response) => return Ok(response),
117+
Ok(response) => {
118+
// If the original request was for v2 but we had to fall back to v1, return a v2
119+
// response
120+
if request_api_version == BuilderApiVersion::V1 &&
121+
api_version != request_api_version
122+
{
123+
return Ok(None);
124+
}
125+
return Ok(response);
126+
}
117127

118128
Err(err) if err.should_retry() => {
119129
tokio::time::sleep(backoff).await;
@@ -127,12 +137,15 @@ async fn submit_block_with_timeout(
127137
}
128138
}
129139

130-
Err(err) if err.is_not_found() && matches!(api_version, BuilderApiVersion::V2) => {
140+
Err(err)
141+
if err.is_not_found() && matches!(request_api_version, BuilderApiVersion::V2) =>
142+
{
131143
warn!(
132144
relay_id = relay.id.as_ref(),
133145
"relay does not support v2 endpoint, retrying with v1"
134146
);
135147
url = relay.submit_block_url(BuilderApiVersion::V1)?;
148+
request_api_version = BuilderApiVersion::V1;
136149
}
137150

138151
Err(err) => return Err(err),

tests/src/mock_relay.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ pub struct MockRelayState {
4646
pub chain: Chain,
4747
pub signer: BlsSecretKey,
4848
large_body: bool,
49+
supports_submit_block_v2: bool,
50+
use_not_found_for_submit_block: bool,
4951
received_get_header: Arc<AtomicU64>,
5052
received_get_status: Arc<AtomicU64>,
5153
received_register_validator: Arc<AtomicU64>,
@@ -69,6 +71,12 @@ impl MockRelayState {
6971
pub fn large_body(&self) -> bool {
7072
self.large_body
7173
}
74+
pub fn supports_submit_block_v2(&self) -> bool {
75+
self.supports_submit_block_v2
76+
}
77+
pub fn use_not_found_for_submit_block(&self) -> bool {
78+
self.use_not_found_for_submit_block
79+
}
7280
pub fn set_response_override(&self, status: StatusCode) {
7381
*self.response_override.write().unwrap() = Some(status);
7482
}
@@ -80,6 +88,8 @@ impl MockRelayState {
8088
chain,
8189
signer,
8290
large_body: false,
91+
supports_submit_block_v2: true,
92+
use_not_found_for_submit_block: false,
8393
received_get_header: Default::default(),
8494
received_get_status: Default::default(),
8595
received_register_validator: Default::default(),
@@ -91,6 +101,14 @@ impl MockRelayState {
91101
pub fn with_large_body(self) -> Self {
92102
Self { large_body: true, ..self }
93103
}
104+
105+
pub fn with_no_submit_block_v2(self) -> Self {
106+
Self { supports_submit_block_v2: false, ..self }
107+
}
108+
109+
pub fn with_not_found_for_submit_block(self) -> Self {
110+
Self { use_not_found_for_submit_block: true, ..self }
111+
}
94112
}
95113

96114
pub fn mock_relay_app_router(state: Arc<MockRelayState>) -> Router {
@@ -100,7 +118,11 @@ pub fn mock_relay_app_router(state: Arc<MockRelayState>) -> Router {
100118
.route(REGISTER_VALIDATOR_PATH, post(handle_register_validator))
101119
.route(SUBMIT_BLOCK_PATH, post(handle_submit_block_v1));
102120

103-
let v2_builder_routes = Router::new().route(SUBMIT_BLOCK_PATH, post(handle_submit_block_v2));
121+
let v2_builder_routes = if state.supports_submit_block_v2 {
122+
Router::new().route(SUBMIT_BLOCK_PATH, post(handle_submit_block_v2))
123+
} else {
124+
Router::new()
125+
};
104126

105127
let builder_router_v1 = Router::new().nest(BUILDER_V1_API_PATH, v1_builder_routes);
106128
let builder_router_v2 = Router::new().nest(BUILDER_V2_API_PATH, v2_builder_routes);
@@ -165,6 +187,9 @@ async fn handle_submit_block_v1(
165187
State(state): State<Arc<MockRelayState>>,
166188
Json(submit_block): Json<SignedBlindedBeaconBlock>,
167189
) -> Response {
190+
if state.use_not_found_for_submit_block() {
191+
return StatusCode::NOT_FOUND.into_response();
192+
}
168193
state.received_submit_block.fetch_add(1, Ordering::Relaxed);
169194
if state.large_body() {
170195
(StatusCode::OK, Json(vec![1u8; 1 + MAX_SIZE_SUBMIT_BLOCK_RESPONSE])).into_response()
@@ -192,6 +217,9 @@ async fn handle_submit_block_v1(
192217
}
193218
}
194219
async fn handle_submit_block_v2(State(state): State<Arc<MockRelayState>>) -> Response {
220+
if state.use_not_found_for_submit_block() {
221+
return StatusCode::NOT_FOUND.into_response();
222+
}
195223
state.received_submit_block.fetch_add(1, Ordering::Relaxed);
196224
(StatusCode::ACCEPTED, "").into_response()
197225
}

tests/tests/pbs_post_blinded_blocks.rs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use tracing::info;
1717

1818
#[tokio::test]
1919
async fn test_submit_block_v1() -> Result<()> {
20-
let res = submit_block_impl(3800, &BuilderApiVersion::V1).await?;
20+
let res = submit_block_impl(3800, &BuilderApiVersion::V1, false, false).await?;
2121
assert_eq!(res.status(), StatusCode::OK);
2222

2323
let signed_blinded_block = load_test_signed_blinded_block();
@@ -32,12 +32,31 @@ async fn test_submit_block_v1() -> Result<()> {
3232

3333
#[tokio::test]
3434
async fn test_submit_block_v2() -> Result<()> {
35-
let res = submit_block_impl(3850, &BuilderApiVersion::V2).await?;
35+
let res = submit_block_impl(3802, &BuilderApiVersion::V2, false, false).await?;
3636
assert_eq!(res.status(), StatusCode::ACCEPTED);
3737
assert_eq!(res.bytes().await?.len(), 0);
3838
Ok(())
3939
}
4040

41+
// Test that when submitting a block using v2 to a relay that does not support
42+
// v2, PBS falls back to v1 and successfully submits the block.
43+
#[tokio::test]
44+
async fn test_submit_block_v2_without_relay_support() -> Result<()> {
45+
let res = submit_block_impl(3804, &BuilderApiVersion::V2, true, false).await?;
46+
assert_eq!(res.status(), StatusCode::ACCEPTED);
47+
assert_eq!(res.bytes().await?.len(), 0);
48+
Ok(())
49+
}
50+
51+
// Test that when submitting a block using v2 to a relay that returns 404s
52+
// for both v1 and v2, PBS doesn't loop forever.
53+
#[tokio::test]
54+
async fn test_submit_block_on_broken_relay() -> Result<()> {
55+
let res = submit_block_impl(3806, &BuilderApiVersion::V2, true, true).await?;
56+
assert_eq!(res.status(), StatusCode::BAD_GATEWAY);
57+
Ok(())
58+
}
59+
4160
#[tokio::test]
4261
async fn test_submit_block_too_large() -> Result<()> {
4362
setup_test_env();
@@ -68,7 +87,12 @@ async fn test_submit_block_too_large() -> Result<()> {
6887
Ok(())
6988
}
7089

71-
async fn submit_block_impl(pbs_port: u16, api_version: &BuilderApiVersion) -> Result<Response> {
90+
async fn submit_block_impl(
91+
pbs_port: u16,
92+
api_version: &BuilderApiVersion,
93+
remove_v2_support: bool,
94+
force_404s: bool,
95+
) -> Result<Response> {
7296
setup_test_env();
7397
let signer = random_secret();
7498
let pubkey = signer.public_key();
@@ -77,7 +101,14 @@ async fn submit_block_impl(pbs_port: u16, api_version: &BuilderApiVersion) -> Re
77101

78102
// Run a mock relay
79103
let relays = vec![generate_mock_relay(pbs_port + 1, pubkey)?];
80-
let mock_state = Arc::new(MockRelayState::new(chain, signer));
104+
let mut mock_relay_state = MockRelayState::new(chain, signer);
105+
if remove_v2_support {
106+
mock_relay_state = mock_relay_state.with_no_submit_block_v2();
107+
}
108+
if force_404s {
109+
mock_relay_state = mock_relay_state.with_not_found_for_submit_block();
110+
}
111+
let mock_state = Arc::new(mock_relay_state);
81112
tokio::spawn(start_mock_relay_service(mock_state.clone(), pbs_port + 1));
82113

83114
// Run the PBS service
@@ -99,6 +130,7 @@ async fn submit_block_impl(pbs_port: u16, api_version: &BuilderApiVersion) -> Re
99130
mock_validator.do_submit_block_v2(Some(signed_blinded_block)).await?
100131
}
101132
};
102-
assert_eq!(mock_state.received_submit_block(), 1);
133+
let expected_count = if force_404s { 0 } else { 1 };
134+
assert_eq!(mock_state.received_submit_block(), expected_count);
103135
Ok(res)
104136
}

0 commit comments

Comments
 (0)