Skip to content

Commit fb5cb6e

Browse files
authored
feat: Implement /eth/v1/validator/prepare_beacon_proposer endpoint (#631)
* feat: Add prepare_beacon_proposer handler and integrate into validator routes * fix: Update prepare_beacon_proposer to use Arc<OperationPool> for thread safety * refactor: Refactor imports and improve formatting in prepare_beacon_proposer handler * fix: Update endpoint URI for prepare_beacon_proposer to remove version prefix * refactor: Rename variable 'req' to 'request' for clarity in tests * refactor: Simplify assertion in test_prepare_beacon_proposer_stores_data * refactor: Improve formatting of assertion in test_prepare_beacon_proposer_stores_data
1 parent 95f3894 commit fb5cb6e

5 files changed

Lines changed: 183 additions & 3 deletions

File tree

crates/common/beacon_api_types/src/request.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use alloy_primitives::B256;
1+
use alloy_primitives::{Address, B256};
22
use ream_bls::BLSSignature;
33
use serde::{Deserialize, Serialize};
44

@@ -10,6 +10,13 @@ pub struct ValidatorsPostRequest {
1010
pub statuses: Option<Vec<ValidatorStatus>>,
1111
}
1212

13+
#[derive(Debug, Deserialize, Serialize)]
14+
pub struct PrepareBeaconProposerItem {
15+
#[serde(with = "serde_utils::quoted_u64")]
16+
pub validator_index: u64,
17+
pub fee_recipient: Address,
18+
}
19+
1320
#[derive(Debug, Deserialize, Serialize)]
1421
pub struct SyncCommitteeRequestItem {
1522
#[serde(with = "serde_utils::quoted_u64")]

crates/common/operation_pool/src/lib.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::collections::HashMap;
22

3-
use alloy_primitives::B256;
3+
use alloy_primitives::{Address, B256};
44
use parking_lot::RwLock;
55
use ream_consensus::{
66
bls_to_execution_change::SignedBLSToExecutionChange, electra::beacon_state::BeaconState,
@@ -12,6 +12,7 @@ use tree_hash::TreeHash;
1212
pub struct OperationPool {
1313
signed_voluntary_exits: RwLock<HashMap<u64, SignedVoluntaryExit>>,
1414
signed_bls_to_execution_changes: RwLock<HashMap<B256, SignedBLSToExecutionChange>>,
15+
proposer_preparations: RwLock<HashMap<u64, Address>>,
1516
}
1617

1718
impl OperationPool {
@@ -60,4 +61,53 @@ impl OperationPool {
6061
pub fn remove_signed_bls_to_execution_change(&self, root: B256) {
6162
self.signed_bls_to_execution_changes.write().remove(&root);
6263
}
64+
65+
pub fn insert_proposer_preparation(&self, validator_index: u64, fee_recipient: Address) {
66+
self.proposer_preparations
67+
.write()
68+
.insert(validator_index, fee_recipient);
69+
}
70+
71+
pub fn get_proposer_preparation(&self, validator_index: u64) -> Option<Address> {
72+
self.proposer_preparations
73+
.read()
74+
.get(&validator_index)
75+
.copied()
76+
}
77+
78+
pub fn get_all_proposer_preparations(&self) -> HashMap<u64, Address> {
79+
self.proposer_preparations.read().clone()
80+
}
81+
}
82+
83+
#[cfg(test)]
84+
mod tests {
85+
use super::*;
86+
87+
#[test]
88+
fn test_proposer_preparation_operations() {
89+
let operation_pool = OperationPool::default();
90+
let fee_recipient1 = Address::from([0x11; 20]);
91+
let fee_recipient2 = Address::from([0x22; 20]);
92+
93+
assert_eq!(operation_pool.get_proposer_preparation(1), None);
94+
95+
operation_pool.insert_proposer_preparation(1, fee_recipient1);
96+
assert_eq!(
97+
operation_pool.get_proposer_preparation(1),
98+
Some(fee_recipient1)
99+
);
100+
101+
operation_pool.insert_proposer_preparation(2, fee_recipient2);
102+
let all_preparations = operation_pool.get_all_proposer_preparations();
103+
assert_eq!(all_preparations.len(), 2);
104+
assert_eq!(all_preparations.get(&1), Some(&fee_recipient1));
105+
assert_eq!(all_preparations.get(&2), Some(&fee_recipient2));
106+
107+
operation_pool.insert_proposer_preparation(1, fee_recipient2);
108+
assert_eq!(
109+
operation_pool.get_proposer_preparation(1),
110+
Some(fee_recipient2)
111+
);
112+
}
63113
}

crates/rpc/src/handlers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub mod header;
88
pub mod light_client;
99
pub mod peers;
1010
pub mod pool;
11+
pub mod prepare_beacon_proposer;
1112
pub mod state;
1213
pub mod syncing;
1314
pub mod validator;
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use std::sync::Arc;
2+
3+
use actix_web::{
4+
HttpResponse, Responder, post,
5+
web::{Data, Json},
6+
};
7+
use ream_beacon_api_types::{error::ApiError, request::PrepareBeaconProposerItem};
8+
use ream_operation_pool::OperationPool;
9+
10+
#[post("/validator/prepare_beacon_proposer")]
11+
pub async fn prepare_beacon_proposer(
12+
operation_pool: Data<Arc<OperationPool>>,
13+
prepare_beacon_proposer_items: Json<Vec<PrepareBeaconProposerItem>>,
14+
) -> Result<impl Responder, ApiError> {
15+
let items = prepare_beacon_proposer_items.into_inner();
16+
17+
if items.is_empty() {
18+
return Err(ApiError::BadRequest("Empty request body".to_string()));
19+
}
20+
21+
for item in items {
22+
operation_pool.insert_proposer_preparation(item.validator_index, item.fee_recipient);
23+
}
24+
25+
Ok(HttpResponse::Ok().finish())
26+
}
27+
28+
#[cfg(test)]
29+
mod tests {
30+
use std::sync::Arc;
31+
32+
use actix_web::{App, http::StatusCode, test};
33+
use alloy_primitives::Address;
34+
35+
use super::*;
36+
37+
#[actix_web::test]
38+
async fn test_prepare_beacon_proposer_success() {
39+
let operation_pool = Arc::new(OperationPool::default());
40+
let app = test::init_service(
41+
App::new()
42+
.app_data(Data::new(operation_pool))
43+
.service(prepare_beacon_proposer),
44+
)
45+
.await;
46+
47+
let fee_recipient = Address::from([0x42; 20]);
48+
let items = vec![
49+
PrepareBeaconProposerItem {
50+
validator_index: 1,
51+
fee_recipient,
52+
},
53+
PrepareBeaconProposerItem {
54+
validator_index: 2,
55+
fee_recipient,
56+
},
57+
];
58+
59+
let request = test::TestRequest::post()
60+
.uri("/validator/prepare_beacon_proposer")
61+
.set_json(&items)
62+
.to_request();
63+
64+
let response = test::call_service(&app, request).await;
65+
assert_eq!(response.status(), StatusCode::OK);
66+
}
67+
68+
#[actix_web::test]
69+
async fn test_prepare_beacon_proposer_empty_request() {
70+
let operation_pool = Arc::new(OperationPool::default());
71+
let app = test::init_service(
72+
App::new()
73+
.app_data(Data::new(operation_pool))
74+
.service(prepare_beacon_proposer),
75+
)
76+
.await;
77+
78+
let items: Vec<PrepareBeaconProposerItem> = vec![];
79+
80+
let request = test::TestRequest::post()
81+
.uri("/validator/prepare_beacon_proposer")
82+
.set_json(&items)
83+
.to_request();
84+
85+
let response = test::call_service(&app, request).await;
86+
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
87+
}
88+
89+
#[actix_web::test]
90+
async fn test_prepare_beacon_proposer_stores_data() {
91+
let operation_pool = Arc::new(OperationPool::default());
92+
let app = test::init_service(
93+
App::new()
94+
.app_data(Data::new(operation_pool.clone()))
95+
.service(prepare_beacon_proposer),
96+
)
97+
.await;
98+
99+
let fee_recipient = Address::from([0x42; 20]);
100+
let items = vec![PrepareBeaconProposerItem {
101+
validator_index: 123,
102+
fee_recipient,
103+
}];
104+
105+
let request = test::TestRequest::post()
106+
.uri("/validator/prepare_beacon_proposer")
107+
.set_json(&items)
108+
.to_request();
109+
110+
let response = test::call_service(&app, request).await;
111+
assert_eq!(response.status(), StatusCode::OK);
112+
113+
assert_eq!(
114+
operation_pool.get_proposer_preparation(123),
115+
Some(fee_recipient)
116+
);
117+
}
118+
}

crates/rpc/src/routes/validator.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
use actix_web::web::ServiceConfig;
22

3-
use crate::handlers::duties::{get_attester_duties, get_proposer_duties};
3+
use crate::handlers::{
4+
duties::{get_attester_duties, get_proposer_duties},
5+
prepare_beacon_proposer::prepare_beacon_proposer,
6+
};
47

58
pub fn register_validator_routes(config: &mut ServiceConfig) {
69
config.service(get_proposer_duties);
710
config.service(get_attester_duties);
11+
config.service(prepare_beacon_proposer);
812
}

0 commit comments

Comments
 (0)