@@ -10,6 +10,7 @@ use lru::LruCache;
10
10
use sensitive_url:: SensitiveUrl ;
11
11
use slog:: { crit, debug, error, info, Logger } ;
12
12
use slot_clock:: SlotClock ;
13
+ use std:: collections:: HashMap ;
13
14
use std:: future:: Future ;
14
15
use std:: sync:: Arc ;
15
16
use std:: time:: Duration ;
@@ -18,7 +19,7 @@ use tokio::{
18
19
sync:: { Mutex , MutexGuard } ,
19
20
time:: { sleep, sleep_until, Instant } ,
20
21
} ;
21
- use types:: ChainSpec ;
22
+ use types:: { ChainSpec , Epoch , ProposerPreparationData } ;
22
23
23
24
pub use engine_api:: { http:: HttpJsonRpc , ExecutePayloadResponseStatus } ;
24
25
@@ -30,6 +31,16 @@ pub mod test_utils;
30
31
/// in an LRU cache to avoid redundant lookups. This is the size of that cache.
31
32
const EXECUTION_BLOCKS_LRU_CACHE_SIZE : usize = 128 ;
32
33
34
+ /// A fee recipient address for use during block production. Only used as a very last resort if
35
+ /// there is no address provided by the user.
36
+ ///
37
+ /// ## Note
38
+ ///
39
+ /// This is *not* the zero-address, since Geth has been known to return errors for a coinbase of
40
+ /// 0x00..00.
41
+ const DEFAULT_SUGGESTED_FEE_RECIPIENT : [ u8 ; 20 ] =
42
+ [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 ] ;
43
+
33
44
#[ derive( Debug ) ]
34
45
pub enum Error {
35
46
NoEngines ,
@@ -46,9 +57,16 @@ impl From<ApiError> for Error {
46
57
}
47
58
}
48
59
60
+ #[ derive( Clone ) ]
61
+ pub struct ProposerPreparationDataEntry {
62
+ update_epoch : Epoch ,
63
+ preparation_data : ProposerPreparationData ,
64
+ }
65
+
49
66
struct Inner {
50
67
engines : Engines < HttpJsonRpc > ,
51
68
suggested_fee_recipient : Option < Address > ,
69
+ proposer_preparation_data : Mutex < HashMap < u64 , ProposerPreparationDataEntry > > ,
52
70
execution_blocks : Mutex < LruCache < Hash256 , ExecutionBlock > > ,
53
71
executor : TaskExecutor ,
54
72
log : Logger ,
@@ -96,6 +114,7 @@ impl ExecutionLayer {
96
114
log : log. clone ( ) ,
97
115
} ,
98
116
suggested_fee_recipient,
117
+ proposer_preparation_data : Mutex :: new ( HashMap :: new ( ) ) ,
99
118
execution_blocks : Mutex :: new ( LruCache :: new ( EXECUTION_BLOCKS_LRU_CACHE_SIZE ) ) ,
100
119
executor,
101
120
log,
@@ -116,17 +135,18 @@ impl ExecutionLayer {
116
135
& self . inner . executor
117
136
}
118
137
119
- fn suggested_fee_recipient ( & self ) -> Result < Address , Error > {
120
- self . inner
121
- . suggested_fee_recipient
122
- . ok_or ( Error :: FeeRecipientUnspecified )
123
- }
124
-
125
138
/// Note: this function returns a mutex guard, be careful to avoid deadlocks.
126
139
async fn execution_blocks ( & self ) -> MutexGuard < ' _ , LruCache < Hash256 , ExecutionBlock > > {
127
140
self . inner . execution_blocks . lock ( ) . await
128
141
}
129
142
143
+ /// Note: this function returns a mutex guard, be careful to avoid deadlocks.
144
+ async fn proposer_preparation_data (
145
+ & self ,
146
+ ) -> MutexGuard < ' _ , HashMap < u64 , ProposerPreparationDataEntry > > {
147
+ self . inner . proposer_preparation_data . lock ( ) . await
148
+ }
149
+
130
150
fn log ( & self ) -> & Logger {
131
151
& self . inner . log
132
152
}
@@ -234,11 +254,124 @@ impl ExecutionLayer {
234
254
self . engines ( ) . upcheck_not_synced ( Logging :: Disabled ) . await ;
235
255
}
236
256
257
+ /// Spawns a routine which cleans the cached proposer preparations periodically.
258
+ pub fn spawn_clean_proposer_preparation_routine < S : SlotClock + ' static , T : EthSpec > (
259
+ & self ,
260
+ slot_clock : S ,
261
+ ) {
262
+ let preparation_cleaner = |el : ExecutionLayer | async move {
263
+ // Start the loop to periodically clean proposer preparation cache.
264
+ loop {
265
+ if let Some ( duration_to_next_epoch) =
266
+ slot_clock. duration_to_next_epoch ( T :: slots_per_epoch ( ) )
267
+ {
268
+ // Wait for next epoch
269
+ sleep ( duration_to_next_epoch) . await ;
270
+
271
+ match slot_clock
272
+ . now ( )
273
+ . map ( |slot| slot. epoch ( T :: slots_per_epoch ( ) ) )
274
+ {
275
+ Some ( current_epoch) => el
276
+ . clean_proposer_preparation ( current_epoch)
277
+ . await
278
+ . map_err ( |e| {
279
+ error ! (
280
+ el. log( ) ,
281
+ "Failed to clean proposer preparation cache" ;
282
+ "error" => format!( "{:?}" , e)
283
+ )
284
+ } )
285
+ . unwrap_or ( ( ) ) ,
286
+ None => error ! ( el. log( ) , "Failed to get current epoch from slot clock" ) ,
287
+ }
288
+ } else {
289
+ error ! ( el. log( ) , "Failed to read slot clock" ) ;
290
+ // If we can't read the slot clock, just wait another slot and retry.
291
+ sleep ( slot_clock. slot_duration ( ) ) . await ;
292
+ }
293
+ }
294
+ } ;
295
+
296
+ self . spawn ( preparation_cleaner, "exec_preparation_cleanup" ) ;
297
+ }
298
+
237
299
/// Returns `true` if there is at least one synced and reachable engine.
238
300
pub async fn is_synced ( & self ) -> bool {
239
301
self . engines ( ) . any_synced ( ) . await
240
302
}
241
303
304
+ /// Updates the proposer preparation data provided by validators
305
+ pub fn update_proposer_preparation_blocking (
306
+ & self ,
307
+ update_epoch : Epoch ,
308
+ preparation_data : & [ ProposerPreparationData ] ,
309
+ ) -> Result < ( ) , Error > {
310
+ self . block_on_generic ( |_| async move {
311
+ self . update_proposer_preparation ( update_epoch, preparation_data)
312
+ . await
313
+ } ) ?
314
+ }
315
+
316
+ /// Updates the proposer preparation data provided by validators
317
+ async fn update_proposer_preparation (
318
+ & self ,
319
+ update_epoch : Epoch ,
320
+ preparation_data : & [ ProposerPreparationData ] ,
321
+ ) -> Result < ( ) , Error > {
322
+ let mut proposer_preparation_data = self . proposer_preparation_data ( ) . await ;
323
+ for preparation_entry in preparation_data {
324
+ proposer_preparation_data. insert (
325
+ preparation_entry. validator_index ,
326
+ ProposerPreparationDataEntry {
327
+ update_epoch,
328
+ preparation_data : preparation_entry. clone ( ) ,
329
+ } ,
330
+ ) ;
331
+ }
332
+
333
+ Ok ( ( ) )
334
+ }
335
+
336
+ /// Removes expired entries from cached proposer preparations
337
+ async fn clean_proposer_preparation ( & self , current_epoch : Epoch ) -> Result < ( ) , Error > {
338
+ let mut proposer_preparation_data = self . proposer_preparation_data ( ) . await ;
339
+
340
+ // Keep all entries that have been updated in the last 2 epochs
341
+ let retain_epoch = current_epoch. saturating_sub ( Epoch :: new ( 2 ) ) ;
342
+ proposer_preparation_data. retain ( |_validator_index, preparation_entry| {
343
+ preparation_entry. update_epoch >= retain_epoch
344
+ } ) ;
345
+
346
+ Ok ( ( ) )
347
+ }
348
+
349
+ /// Returns the fee-recipient address that should be used to build a block
350
+ async fn get_suggested_fee_recipient ( & self , proposer_index : u64 ) -> Address {
351
+ if let Some ( preparation_data_entry) =
352
+ self . proposer_preparation_data ( ) . await . get ( & proposer_index)
353
+ {
354
+ // The values provided via the API have first priority.
355
+ preparation_data_entry. preparation_data . fee_recipient
356
+ } else if let Some ( address) = self . inner . suggested_fee_recipient {
357
+ // If there has been no fee recipient provided via the API, but the BN has been provided
358
+ // with a global default address, use that.
359
+ address
360
+ } else {
361
+ // If there is no user-provided fee recipient, use a junk value and complain loudly.
362
+ crit ! (
363
+ self . log( ) ,
364
+ "Fee recipient unknown" ;
365
+ "msg" => "the suggested_fee_recipient was unknown during block production. \
366
+ a junk address was used, rewards were lost! \
367
+ check the --suggested-fee-recipient flag and VC configuration.",
368
+ "proposer_index" => ?proposer_index
369
+ ) ;
370
+
371
+ Address :: from_slice ( & DEFAULT_SUGGESTED_FEE_RECIPIENT )
372
+ }
373
+ }
374
+
242
375
/// Maps to the `engine_getPayload` JSON-RPC call.
243
376
///
244
377
/// However, it will attempt to call `self.prepare_payload` if it cannot find an existing
@@ -254,8 +387,10 @@ impl ExecutionLayer {
254
387
timestamp : u64 ,
255
388
random : Hash256 ,
256
389
finalized_block_hash : Hash256 ,
390
+ proposer_index : u64 ,
257
391
) -> Result < ExecutionPayload < T > , Error > {
258
- let suggested_fee_recipient = self . suggested_fee_recipient ( ) ?;
392
+ let suggested_fee_recipient = self . get_suggested_fee_recipient ( proposer_index) . await ;
393
+
259
394
debug ! (
260
395
self . log( ) ,
261
396
"Issuing engine_getPayload" ;
0 commit comments