mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-10 20:22:02 +00:00
* Checkout serde_utils from rayonism
* Make eth1::http functions pub
* Add bones of execution_layer
* Modify decoding
* Expose Transaction, cargo fmt
* Add executePayload
* Add all minimal spec endpoints
* Start adding json rpc wrapper
* Finish custom JSON response handler
* Switch to new rpc sending method
* Add first test
* Fix camelCase
* Finish adding tests
* Begin threading execution layer into BeaconChain
* Fix clippy lints
* Fix clippy lints
* Thread execution layer into ClientBuilder
* Add CLI flags
* Add block processing methods to ExecutionLayer
* Add block_on to execution_layer
* Integrate execute_payload
* Add extra_data field
* Begin implementing payload handle
* Send consensus valid/invalid messages
* Fix minor type in task_executor
* Call forkchoiceUpdated
* Add search for TTD block
* Thread TTD into execution layer
* Allow producing block with execution payload
* Add LRU cache for execution blocks
* Remove duplicate 0x on ssz_types serialization
* Add tests for block getter methods
* Add basic block generator impl
* Add is_valid_terminal_block to EL
* Verify merge block in block_verification
* Partially implement --terminal-block-hash-override
* Add terminal_block_hash to ChainSpec
* Remove Option from terminal_block_hash in EL
* Revert merge changes to consensus/fork_choice
* Remove commented-out code
* Add bones for handling RPC methods on test server
* Add first ExecutionLayer tests
* Add testing for finding terminal block
* Prevent infinite loops
* Add insert_merge_block to block gen
* Add block gen test for pos blocks
* Start adding payloads to block gen
* Fix clippy lints
* Add execution payload to block gen
* Add execute_payload to block_gen
* Refactor block gen
* Add all routes to mock server
* Use Uint256 for base_fee_per_gas
* Add working execution chain build
* Remove unused var
* Revert "Use Uint256 for base_fee_per_gas"
This reverts commit 6c88f19ac4.
* Fix base_fee_for_gas Uint256
* Update execute payload handle
* Improve testing, fix bugs
* Fix default fee-recipient
* Fix fee-recipient address (again)
* Add check for terminal block, add comments, tidy
* Apply suggestions from code review
Co-authored-by: realbigsean <seananderson33@GMAIL.com>
* Fix is_none on handle Drop
* Remove commented-out tests
Co-authored-by: realbigsean <seananderson33@GMAIL.com>
240 lines
7.3 KiB
Rust
240 lines
7.3 KiB
Rust
//! Provides generic behaviour for multiple execution engines, specifically fallback behaviour.
|
|
|
|
use crate::engine_api::{EngineApi, Error as EngineApiError};
|
|
use futures::future::join_all;
|
|
use slog::{crit, error, info, warn, Logger};
|
|
use std::future::Future;
|
|
use tokio::sync::RwLock;
|
|
|
|
/// Stores the remembered state of a engine.
|
|
#[derive(Copy, Clone, PartialEq)]
|
|
enum EngineState {
|
|
Online,
|
|
Offline,
|
|
}
|
|
|
|
impl EngineState {
|
|
fn set_online(&mut self) {
|
|
*self = EngineState::Online
|
|
}
|
|
|
|
fn set_offline(&mut self) {
|
|
*self = EngineState::Offline
|
|
}
|
|
|
|
fn is_online(&self) -> bool {
|
|
*self == EngineState::Online
|
|
}
|
|
|
|
fn is_offline(&self) -> bool {
|
|
*self == EngineState::Offline
|
|
}
|
|
}
|
|
|
|
/// An execution engine.
|
|
pub struct Engine<T> {
|
|
pub id: String,
|
|
pub api: T,
|
|
state: RwLock<EngineState>,
|
|
}
|
|
|
|
impl<T> Engine<T> {
|
|
/// Creates a new, offline engine.
|
|
pub fn new(id: String, api: T) -> Self {
|
|
Self {
|
|
id,
|
|
api,
|
|
state: RwLock::new(EngineState::Offline),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Holds multiple execution engines and provides functionality for managing them in a fallback
|
|
/// manner.
|
|
pub struct Engines<T> {
|
|
pub engines: Vec<Engine<T>>,
|
|
pub log: Logger,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum EngineError {
|
|
Offline { id: String },
|
|
Api { id: String, error: EngineApiError },
|
|
}
|
|
|
|
impl<T: EngineApi> Engines<T> {
|
|
/// Run the `EngineApi::upcheck` function on all nodes which are currently offline.
|
|
///
|
|
/// This can be used to try and recover any offline nodes.
|
|
async fn upcheck_offline(&self) {
|
|
let upcheck_futures = self.engines.iter().map(|engine| async move {
|
|
let mut state = engine.state.write().await;
|
|
if state.is_offline() {
|
|
match engine.api.upcheck().await {
|
|
Ok(()) => {
|
|
info!(
|
|
self.log,
|
|
"Execution engine online";
|
|
"id" => &engine.id
|
|
);
|
|
state.set_online()
|
|
}
|
|
Err(e) => {
|
|
warn!(
|
|
self.log,
|
|
"Execution engine offline";
|
|
"error" => ?e,
|
|
"id" => &engine.id
|
|
)
|
|
}
|
|
}
|
|
}
|
|
*state
|
|
});
|
|
|
|
let num_online = join_all(upcheck_futures)
|
|
.await
|
|
.into_iter()
|
|
.filter(|state: &EngineState| state.is_online())
|
|
.count();
|
|
|
|
if num_online == 0 {
|
|
crit!(
|
|
self.log,
|
|
"No execution engines online";
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Run `func` on all engines, in the order in which they are defined, returning the first
|
|
/// successful result that is found.
|
|
///
|
|
/// This function might try to run `func` twice. If all nodes return an error on the first time
|
|
/// it runs, it will try to upcheck all offline nodes and then run the function again.
|
|
pub async fn first_success<'a, F, G, H>(&'a self, func: F) -> Result<H, Vec<EngineError>>
|
|
where
|
|
F: Fn(&'a Engine<T>) -> G + Copy,
|
|
G: Future<Output = Result<H, EngineApiError>>,
|
|
{
|
|
match self.first_success_without_retry(func).await {
|
|
Ok(result) => Ok(result),
|
|
Err(mut first_errors) => {
|
|
// Try to recover some nodes.
|
|
self.upcheck_offline().await;
|
|
// Retry the call on all nodes.
|
|
match self.first_success_without_retry(func).await {
|
|
Ok(result) => Ok(result),
|
|
Err(second_errors) => {
|
|
first_errors.extend(second_errors);
|
|
Err(first_errors)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Run `func` on all engines, in the order in which they are defined, returning the first
|
|
/// successful result that is found.
|
|
async fn first_success_without_retry<'a, F, G, H>(
|
|
&'a self,
|
|
func: F,
|
|
) -> Result<H, Vec<EngineError>>
|
|
where
|
|
F: Fn(&'a Engine<T>) -> G,
|
|
G: Future<Output = Result<H, EngineApiError>>,
|
|
{
|
|
let mut errors = vec![];
|
|
|
|
for engine in &self.engines {
|
|
let engine_online = engine.state.read().await.is_online();
|
|
if engine_online {
|
|
match func(engine).await {
|
|
Ok(result) => return Ok(result),
|
|
Err(error) => {
|
|
error!(
|
|
self.log,
|
|
"Execution engine call failed";
|
|
"error" => ?error,
|
|
"id" => &engine.id
|
|
);
|
|
engine.state.write().await.set_offline();
|
|
errors.push(EngineError::Api {
|
|
id: engine.id.clone(),
|
|
error,
|
|
})
|
|
}
|
|
}
|
|
} else {
|
|
errors.push(EngineError::Offline {
|
|
id: engine.id.clone(),
|
|
})
|
|
}
|
|
}
|
|
|
|
Err(errors)
|
|
}
|
|
|
|
/// Runs `func` on all nodes concurrently, returning all results.
|
|
///
|
|
/// This function might try to run `func` twice. If all nodes return an error on the first time
|
|
/// it runs, it will try to upcheck all offline nodes and then run the function again.
|
|
pub async fn broadcast<'a, F, G, H>(&'a self, func: F) -> Vec<Result<H, EngineError>>
|
|
where
|
|
F: Fn(&'a Engine<T>) -> G + Copy,
|
|
G: Future<Output = Result<H, EngineApiError>>,
|
|
{
|
|
let first_results = self.broadcast_without_retry(func).await;
|
|
|
|
let mut any_offline = false;
|
|
for result in &first_results {
|
|
match result {
|
|
Ok(_) => return first_results,
|
|
Err(EngineError::Offline { .. }) => any_offline = true,
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
if any_offline {
|
|
self.upcheck_offline().await;
|
|
self.broadcast_without_retry(func).await
|
|
} else {
|
|
first_results
|
|
}
|
|
}
|
|
|
|
/// Runs `func` on all nodes concurrently, returning all results.
|
|
pub async fn broadcast_without_retry<'a, F, G, H>(
|
|
&'a self,
|
|
func: F,
|
|
) -> Vec<Result<H, EngineError>>
|
|
where
|
|
F: Fn(&'a Engine<T>) -> G,
|
|
G: Future<Output = Result<H, EngineApiError>>,
|
|
{
|
|
let func = &func;
|
|
let futures = self.engines.iter().map(|engine| async move {
|
|
let engine_online = engine.state.read().await.is_online();
|
|
if engine_online {
|
|
func(engine).await.map_err(|error| {
|
|
error!(
|
|
self.log,
|
|
"Execution engine call failed";
|
|
"error" => ?error,
|
|
"id" => &engine.id
|
|
);
|
|
EngineError::Api {
|
|
id: engine.id.clone(),
|
|
error,
|
|
}
|
|
})
|
|
} else {
|
|
Err(EngineError::Offline {
|
|
id: engine.id.clone(),
|
|
})
|
|
}
|
|
});
|
|
|
|
join_all(futures).await
|
|
}
|
|
}
|