@@ -3,6 +3,7 @@ use alloy_rpc_types_engine::{
3
3
ExecutionPayload , ExecutionPayloadV3 , ForkchoiceState , ForkchoiceUpdated , PayloadId ,
4
4
PayloadStatus ,
5
5
} ;
6
+ use futures:: future:: join_all;
6
7
use jsonrpsee:: core:: { async_trait, ClientError , RpcResult } ;
7
8
use jsonrpsee:: http_client:: transport:: HttpBackend ;
8
9
use jsonrpsee:: http_client:: HttpClient ;
@@ -16,6 +17,8 @@ use reth_rpc_layer::AuthClientService;
16
17
use std:: sync:: Arc ;
17
18
use tracing:: { error, info} ;
18
19
20
+ use crate :: selector:: { DefaultPayloadSelector , PayloadSelector } ;
21
+
19
22
#[ rpc( server, client, namespace = "engine" ) ]
20
23
pub trait EngineApi {
21
24
#[ method( name = "forkchoiceUpdatedV3" ) ]
@@ -42,20 +45,22 @@ pub trait EngineApi {
42
45
43
46
pub struct EthEngineApi < S = AuthClientService < HttpBackend > > {
44
47
l2_client : Arc < HttpClient < S > > ,
45
- builder_client : Arc < HttpClient < S > > ,
48
+ builder_clients : Vec < Arc < HttpClient < S > > > ,
49
+ payload_selector : Arc < dyn PayloadSelector + Send + Sync > ,
46
50
boost_sync : bool ,
47
51
}
48
52
49
53
impl < S > EthEngineApi < S > {
50
54
pub fn new (
51
55
l2_client : Arc < HttpClient < S > > ,
52
- builder_client : Arc < HttpClient < S > > ,
56
+ builder_clients : Vec < Arc < HttpClient < S > > > ,
53
57
boost_sync : bool ,
54
58
) -> Self {
55
59
Self {
56
60
l2_client,
57
- builder_client ,
61
+ builder_clients ,
58
62
boost_sync,
63
+ payload_selector : Arc :: new ( DefaultPayloadSelector ) ,
59
64
}
60
65
}
61
66
}
@@ -85,11 +90,12 @@ impl EngineApiServer for EthEngineApi {
85
90
} ;
86
91
87
92
if should_send_to_builder {
88
- // async call to builder to trigger payload building and sync
89
- let builder = self . builder_client . clone ( ) ;
90
- let attr = payload_attributes. clone ( ) ;
91
- tokio:: spawn ( async move {
92
- builder. fork_choice_updated_v3 ( fork_choice_state, attr) . await . map ( |response| {
93
+ // async call to each builder to trigger payload building and sync
94
+ for builder in self . builder_clients . iter ( ) {
95
+ let builder = builder. clone ( ) ;
96
+ let attr = payload_attributes. clone ( ) ;
97
+ tokio:: spawn ( async move {
98
+ builder. fork_choice_updated_v3 ( fork_choice_state, attr) . await . map ( |response| {
93
99
let payload_id_str = response. payload_id . map ( |id| id. to_string ( ) ) . unwrap_or_default ( ) ;
94
100
if response. is_invalid ( ) {
95
101
error ! ( message = "builder rejected fork_choice_updated_v3 with attributes" , "payload_id" = payload_id_str, "validation_error" = %response. payload_status. status) ;
@@ -99,7 +105,8 @@ impl EngineApiServer for EthEngineApi {
99
105
} ) . map_err ( |e| {
100
106
error ! ( message = "error calling fork_choice_updated_v3 to builder" , "error" = %e, "head_block_hash" = %fork_choice_state. head_block_hash) ;
101
107
} )
102
- } ) ;
108
+ } ) ;
109
+ }
103
110
} else {
104
111
info ! ( message = "no payload attributes provided or no_tx_pool is set" , "head_block_hash" = %fork_choice_state. head_block_hash) ;
105
112
}
@@ -126,47 +133,53 @@ impl EngineApiServer for EthEngineApi {
126
133
) -> RpcResult < OptimismExecutionPayloadEnvelopeV3 > {
127
134
info ! ( message = "received get_payload_v3" , "payload_id" = %payload_id) ;
128
135
let l2_client_future = self . l2_client . get_payload_v3 ( payload_id) ;
129
- let builder_client_future = Box :: pin ( async {
130
- let payload = self . builder_client . get_payload_v3 ( payload_id) . await . map_err ( |e| {
131
- error ! ( message = "error calling get_payload_v3 from builder" , "error" = %e, "payload_id" = %payload_id) ;
132
- e
133
- } ) ?;
134
-
135
- info ! ( message = "received payload from builder" , "payload_id" = %payload_id, "block_hash" = %payload. as_v1_payload( ) . block_hash) ;
136
-
137
- // Send the payload to the local execution engine with engine_newPayload to validate the block from the builder.
138
- // Otherwise, we do not want to risk the network to a halt since op-node will not be able to propose the block.
139
- // If validation fails, return the local block since that one has already been validated.
140
- let payload_status = self . l2_client . new_payload_v3 ( payload. execution_payload . clone ( ) , vec ! [ ] , payload. parent_beacon_block_root ) . await . map_err ( |e| {
141
- error ! ( message = "error calling new_payload_v3 to validate builder payload" , "error" = %e, "payload_id" = %payload_id) ;
142
- e
143
- } ) ?;
144
- if payload_status. is_invalid ( ) {
136
+ let builder_client_futures = self . builder_clients . iter ( ) . map ( |builder| {
137
+ let builder = builder. clone ( ) ;
138
+ Box :: pin ( async move {
139
+ let payload = builder. get_payload_v3 ( payload_id) . await . map_err ( |e| {
140
+ error ! ( message = "error calling get_payload_v3 from builder" , "error" = %e, "payload_id" = %payload_id) ;
141
+ e
142
+ } ) ?;
143
+
144
+ info ! ( message = "received payload from builder" , "payload_id" = %payload_id, "block_hash" = %payload. as_v1_payload( ) . block_hash) ;
145
+
146
+ // Send the payload to the local execution engine with engine_newPayload to validate the block from the builder.
147
+ // Otherwise, we do not want to risk the network to a halt since op-node will not be able to propose the block.
148
+ // If validation fails, return the local block since that one has already been validated.
149
+ let payload_status = self . l2_client . new_payload_v3 ( payload. execution_payload . clone ( ) , vec ! [ ] , payload. parent_beacon_block_root ) . await . map_err ( |e| {
150
+ error ! ( message = "error calling new_payload_v3 to validate builder payload" , "error" = %e, "payload_id" = %payload_id) ;
151
+ e
152
+ } ) ?;
153
+ if payload_status. is_invalid ( ) {
145
154
error ! ( message = "builder payload was not valid" , "payload_status" = %payload_status. status, "payload_id" = %payload_id) ;
146
155
Err ( ClientError :: Call ( ErrorObject :: owned (
147
156
INVALID_REQUEST_CODE ,
148
157
"Builder payload was not valid" ,
149
158
None :: < String > ,
150
- ) ) )
151
- } else {
152
- info ! ( message = "received payload status from local execution engine validating builder payload" , "payload_id" = %payload_id) ;
153
- Ok ( payload)
154
- }
155
- } ) ;
156
-
157
- let ( l2_payload, builder_payload) = tokio:: join!( l2_client_future, builder_client_future) ;
158
-
159
- builder_payload. or ( l2_payload) . map_err ( |e| match e {
160
- ClientError :: Call ( err) => err, // Already an ErrorObjectOwned, so just return it
161
- other_error => {
162
- error ! (
163
- message = "error calling get_payload_v3" ,
164
- "error" = %other_error,
165
- "payload_id" = %payload_id
166
- ) ;
167
- ErrorCode :: InternalError . into ( )
168
- }
169
- } )
159
+ ) ) )
160
+ } else {
161
+ info ! ( message = "received payload status from local execution engine validating builder payload" , "payload_id" = %payload_id) ;
162
+ Ok ( payload)
163
+ }
164
+ } )
165
+ } ) . collect :: < Vec < _ > > ( ) ;
166
+
167
+ let ( l2_payload, builder_payloads) =
168
+ tokio:: join!( l2_client_future, join_all( builder_client_futures) ) ;
169
+
170
+ self . payload_selector
171
+ . select_payload ( l2_payload, builder_payloads)
172
+ . map_err ( |e| match e {
173
+ ClientError :: Call ( err) => err, // Already an ErrorObjectOwned, so just return it
174
+ other_error => {
175
+ error ! (
176
+ message = "error calling get_payload_v3" ,
177
+ "error" = %other_error,
178
+ "payload_id" = %payload_id
179
+ ) ;
180
+ ErrorCode :: InternalError . into ( )
181
+ }
182
+ } )
170
183
}
171
184
172
185
async fn new_payload_v3 (
@@ -180,11 +193,12 @@ impl EngineApiServer for EthEngineApi {
180
193
181
194
// async call to builder to sync the builder node
182
195
if self . boost_sync {
183
- let builder = self . builder_client . clone ( ) ;
184
- let builder_payload = payload. clone ( ) ;
185
- let builder_versioned_hashes = versioned_hashes. clone ( ) ;
186
- tokio:: spawn ( async move {
187
- builder. new_payload_v3 ( builder_payload, builder_versioned_hashes, parent_beacon_block_root) . await
196
+ for builder in self . builder_clients . iter ( ) {
197
+ let builder = builder. clone ( ) ;
198
+ let builder_payload = payload. clone ( ) ;
199
+ let builder_versioned_hashes = versioned_hashes. clone ( ) ;
200
+ tokio:: spawn ( async move {
201
+ builder. new_payload_v3 ( builder_payload, builder_versioned_hashes, parent_beacon_block_root) . await
188
202
. map ( |response : PayloadStatus | {
189
203
if response. is_invalid ( ) {
190
204
error ! ( message = "builder rejected new_payload_v3" , "block_hash" = %block_hash) ;
@@ -195,7 +209,8 @@ impl EngineApiServer for EthEngineApi {
195
209
error ! ( message = "error calling new_payload_v3 to builder" , "error" = %e, "block_hash" = %block_hash) ;
196
210
e
197
211
} )
198
- } ) ;
212
+ } ) ;
213
+ }
199
214
}
200
215
201
216
self . l2_client
0 commit comments