mirror of
https://github.com/sigp/lighthouse.git
synced 2026-06-17 10:48:28 +00:00
Merge remote-tracking branch 'origin/unstable' into tree-states
This commit is contained in:
@@ -8,17 +8,19 @@ use crate::HttpJsonRpc;
|
||||
use lru::LruCache;
|
||||
use slog::{debug, error, info, warn, Logger};
|
||||
use std::future::Future;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use task_executor::TaskExecutor;
|
||||
use tokio::sync::{watch, Mutex, RwLock};
|
||||
use tokio_stream::wrappers::WatchStream;
|
||||
use types::non_zero_usize::new_non_zero_usize;
|
||||
use types::ExecutionBlockHash;
|
||||
|
||||
/// The number of payload IDs that will be stored for each `Engine`.
|
||||
///
|
||||
/// Since the size of each value is small (~800 bytes) a large number is used for safety.
|
||||
const PAYLOAD_ID_LRU_CACHE_SIZE: usize = 512;
|
||||
const PAYLOAD_ID_LRU_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(512);
|
||||
const CACHED_ENGINE_CAPABILITIES_AGE_LIMIT: Duration = Duration::from_secs(900); // 15 minutes
|
||||
|
||||
/// Stores the remembered state of a engine.
|
||||
|
||||
@@ -29,6 +29,7 @@ use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::io::Write;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||
@@ -42,6 +43,7 @@ use tokio_stream::wrappers::WatchStream;
|
||||
use tree_hash::TreeHash;
|
||||
use types::beacon_block_body::KzgCommitments;
|
||||
use types::builder_bid::BuilderBid;
|
||||
use types::non_zero_usize::new_non_zero_usize;
|
||||
use types::payload::BlockProductionVersion;
|
||||
use types::{
|
||||
AbstractExecPayload, BlobsList, ExecutionPayloadDeneb, KzgProofs, SignedBlindedBeaconBlock,
|
||||
@@ -68,7 +70,7 @@ pub const DEFAULT_JWT_FILE: &str = "jwt.hex";
|
||||
|
||||
/// Each time the `ExecutionLayer` retrieves a block from an execution node, it stores that block
|
||||
/// in an LRU cache to avoid redundant lookups. This is the size of that cache.
|
||||
const EXECUTION_BLOCKS_LRU_CACHE_SIZE: usize = 128;
|
||||
const EXECUTION_BLOCKS_LRU_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(128);
|
||||
|
||||
/// A fee recipient address for use during block production. Only used as a very last resort if
|
||||
/// there is no address provided by the user.
|
||||
@@ -176,15 +178,26 @@ impl<T: EthSpec> From<BlockProposalContents<T, FullPayload<T>>>
|
||||
for BlockProposalContents<T, BlindedPayload<T>>
|
||||
{
|
||||
fn from(item: BlockProposalContents<T, FullPayload<T>>) -> Self {
|
||||
let block_value = item.block_value().to_owned();
|
||||
|
||||
let blinded_payload: BlockProposalContents<T, BlindedPayload<T>> =
|
||||
match item {
|
||||
BlockProposalContents::Payload {
|
||||
payload: item.to_payload().execution_payload().into(),
|
||||
payload,
|
||||
block_value,
|
||||
};
|
||||
|
||||
blinded_payload
|
||||
} => BlockProposalContents::Payload {
|
||||
payload: payload.execution_payload().into(),
|
||||
block_value,
|
||||
},
|
||||
BlockProposalContents::PayloadAndBlobs {
|
||||
payload,
|
||||
block_value,
|
||||
kzg_commitments,
|
||||
blobs_and_proofs: _,
|
||||
} => BlockProposalContents::PayloadAndBlobs {
|
||||
payload: payload.execution_payload().into(),
|
||||
block_value,
|
||||
kzg_commitments,
|
||||
blobs_and_proofs: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,10 +335,7 @@ struct Inner<E: EthSpec> {
|
||||
proposers: RwLock<HashMap<ProposerKey, Proposer>>,
|
||||
executor: TaskExecutor,
|
||||
payload_cache: PayloadCache<E>,
|
||||
builder_profit_threshold: Uint256,
|
||||
log: Logger,
|
||||
always_prefer_builder_payload: bool,
|
||||
ignore_builder_override_suggestion_threshold: f32,
|
||||
/// Track whether the last `newPayload` call errored.
|
||||
///
|
||||
/// This is used *only* in the informational sync status endpoint, so that a VC using this
|
||||
@@ -352,11 +362,7 @@ pub struct Config {
|
||||
pub jwt_version: Option<String>,
|
||||
/// Default directory for the jwt secret if not provided through cli.
|
||||
pub default_datadir: PathBuf,
|
||||
/// The minimum value of an external payload for it to be considered in a proposal.
|
||||
pub builder_profit_threshold: u128,
|
||||
pub execution_timeout_multiplier: Option<u32>,
|
||||
pub always_prefer_builder_payload: bool,
|
||||
pub ignore_builder_override_suggestion_threshold: f32,
|
||||
}
|
||||
|
||||
/// Provides access to one execution engine and provides a neat interface for consumption by the
|
||||
@@ -366,40 +372,6 @@ pub struct ExecutionLayer<T: EthSpec> {
|
||||
inner: Arc<Inner<T>>,
|
||||
}
|
||||
|
||||
/// This function will return the percentage difference between 2 U256 values, using `base_value`
|
||||
/// as the denominator. It is accurate to 7 decimal places which is about the precision of
|
||||
/// an f32.
|
||||
///
|
||||
/// If some error is encountered in the calculation, None will be returned.
|
||||
fn percentage_difference_u256(base_value: Uint256, comparison_value: Uint256) -> Option<f32> {
|
||||
if base_value == Uint256::zero() {
|
||||
return None;
|
||||
}
|
||||
// this is the total supply of ETH in WEI
|
||||
let max_value = Uint256::from(12u8) * Uint256::exp10(25);
|
||||
if base_value > max_value || comparison_value > max_value {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Now we should be able to calculate the difference without division by zero or overflow
|
||||
const PRECISION: usize = 7;
|
||||
let precision_factor = Uint256::exp10(PRECISION);
|
||||
let scaled_difference = if base_value <= comparison_value {
|
||||
(comparison_value - base_value) * precision_factor
|
||||
} else {
|
||||
(base_value - comparison_value) * precision_factor
|
||||
};
|
||||
let scaled_proportion = scaled_difference / base_value;
|
||||
// max value of scaled difference is 1.2 * 10^33, well below the max value of a u128 / f64 / f32
|
||||
let percentage =
|
||||
100.0f64 * scaled_proportion.low_u128() as f64 / precision_factor.low_u128() as f64;
|
||||
if base_value <= comparison_value {
|
||||
Some(percentage as f32)
|
||||
} else {
|
||||
Some(-percentage as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> ExecutionLayer<T> {
|
||||
/// Instantiate `Self` with an Execution engine specified in `Config`, using JSON-RPC via HTTP.
|
||||
pub fn from_config(config: Config, executor: TaskExecutor, log: Logger) -> Result<Self, Error> {
|
||||
@@ -412,10 +384,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
jwt_id,
|
||||
jwt_version,
|
||||
default_datadir,
|
||||
builder_profit_threshold,
|
||||
execution_timeout_multiplier,
|
||||
always_prefer_builder_payload,
|
||||
ignore_builder_override_suggestion_threshold,
|
||||
} = config;
|
||||
|
||||
if urls.len() > 1 {
|
||||
@@ -476,10 +445,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
execution_blocks: Mutex::new(LruCache::new(EXECUTION_BLOCKS_LRU_CACHE_SIZE)),
|
||||
executor,
|
||||
payload_cache: PayloadCache::default(),
|
||||
builder_profit_threshold: Uint256::from(builder_profit_threshold),
|
||||
log,
|
||||
always_prefer_builder_payload,
|
||||
ignore_builder_override_suggestion_threshold,
|
||||
last_new_payload_errored: RwLock::new(false),
|
||||
};
|
||||
|
||||
@@ -517,7 +483,6 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
self.log(),
|
||||
"Using external block builder";
|
||||
"builder_url" => ?builder_url,
|
||||
"builder_profit_threshold" => self.inner.builder_profit_threshold.as_u128(),
|
||||
"local_user_agent" => builder_client.get_user_agent(),
|
||||
);
|
||||
self.inner.builder.swap(Some(Arc::new(builder_client)));
|
||||
@@ -823,6 +788,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
builder_params: BuilderParams,
|
||||
current_fork: ForkName,
|
||||
spec: &ChainSpec,
|
||||
builder_boost_factor: Option<u64>,
|
||||
block_production_version: BlockProductionVersion,
|
||||
) -> Result<BlockProposalContentsType<T>, Error> {
|
||||
let payload_result_type = match block_production_version {
|
||||
@@ -833,6 +799,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
forkchoice_update_params,
|
||||
builder_params,
|
||||
current_fork,
|
||||
builder_boost_factor,
|
||||
spec,
|
||||
)
|
||||
.await
|
||||
@@ -857,6 +824,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
forkchoice_update_params,
|
||||
builder_params,
|
||||
current_fork,
|
||||
None,
|
||||
spec,
|
||||
)
|
||||
.await?
|
||||
@@ -977,6 +945,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
(relay_result, local_result)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn determine_and_fetch_payload(
|
||||
&self,
|
||||
parent_hash: ExecutionBlockHash,
|
||||
@@ -984,6 +953,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
forkchoice_update_params: ForkchoiceUpdateParameters,
|
||||
builder_params: BuilderParams,
|
||||
current_fork: ForkName,
|
||||
builder_boost_factor: Option<u64>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<ProvenancedPayload<BlockProposalContentsType<T>>, Error> {
|
||||
let Some(builder) = self.builder() else {
|
||||
@@ -1135,18 +1105,36 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
)));
|
||||
}
|
||||
|
||||
if self.inner.always_prefer_builder_payload {
|
||||
return ProvenancedPayload::try_from(relay.data.message);
|
||||
}
|
||||
|
||||
let relay_value = *relay.data.message.value();
|
||||
|
||||
let boosted_relay_value = match builder_boost_factor {
|
||||
Some(builder_boost_factor) => {
|
||||
(relay_value / 100).saturating_mul(builder_boost_factor.into())
|
||||
}
|
||||
None => relay_value,
|
||||
};
|
||||
|
||||
let local_value = *local.block_value();
|
||||
|
||||
if local_value >= relay_value {
|
||||
if local_value >= boosted_relay_value {
|
||||
info!(
|
||||
self.log(),
|
||||
"Local block is more profitable than relay block";
|
||||
"local_block_value" => %local_value,
|
||||
"relay_value" => %relay_value,
|
||||
"boosted_relay_value" => %boosted_relay_value,
|
||||
"builder_boost_factor" => ?builder_boost_factor,
|
||||
);
|
||||
return Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full(
|
||||
local.try_into()?,
|
||||
)));
|
||||
}
|
||||
|
||||
if local.should_override_builder().unwrap_or(false) {
|
||||
info!(
|
||||
self.log(),
|
||||
"Using local payload because execution engine suggested we ignore builder payload";
|
||||
"local_block_value" => %local_value,
|
||||
"relay_value" => %relay_value
|
||||
);
|
||||
return Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full(
|
||||
@@ -1154,43 +1142,13 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
)));
|
||||
}
|
||||
|
||||
if relay_value < self.inner.builder_profit_threshold {
|
||||
info!(
|
||||
self.log(),
|
||||
"Builder payload ignored";
|
||||
"info" => "using local payload",
|
||||
"reason" => format!("payload value of {} does not meet user-configured profit-threshold of {}", relay_value, self.inner.builder_profit_threshold),
|
||||
"relay_block_hash" => ?header.block_hash(),
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
return Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full(
|
||||
local.try_into()?,
|
||||
)));
|
||||
}
|
||||
|
||||
if local.should_override_builder().unwrap_or(false) {
|
||||
let percentage_difference =
|
||||
percentage_difference_u256(local_value, relay_value);
|
||||
if percentage_difference.map_or(false, |percentage| {
|
||||
percentage < self.inner.ignore_builder_override_suggestion_threshold
|
||||
}) {
|
||||
info!(
|
||||
self.log(),
|
||||
"Using local payload because execution engine suggested we ignore builder payload";
|
||||
"local_block_value" => %local_value,
|
||||
"relay_value" => %relay_value
|
||||
);
|
||||
return Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full(
|
||||
local.try_into()?,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
self.log(),
|
||||
"Relay block is more profitable than local block";
|
||||
"local_block_value" => %local_value,
|
||||
"relay_value" => %relay_value
|
||||
"relay_value" => %relay_value,
|
||||
"boosted_relay_value" => %boosted_relay_value,
|
||||
"builder_boost_factor" => ?builder_boost_factor
|
||||
);
|
||||
|
||||
Ok(ProvenancedPayload::try_from(relay.data.message)?)
|
||||
@@ -2361,42 +2319,4 @@ mod test {
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn percentage_difference_u256_tests() {
|
||||
// ensure function returns `None` when base value is zero
|
||||
assert_eq!(percentage_difference_u256(0.into(), 1.into()), None);
|
||||
// ensure function returns `None` when either value is greater than 120 Million ETH
|
||||
let max_value = Uint256::from(12u8) * Uint256::exp10(25);
|
||||
assert_eq!(
|
||||
percentage_difference_u256(1u8.into(), max_value + Uint256::from(1u8)),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
percentage_difference_u256(max_value + Uint256::from(1u8), 1u8.into()),
|
||||
None
|
||||
);
|
||||
// it should work up to max value
|
||||
assert_eq!(
|
||||
percentage_difference_u256(max_value, max_value / Uint256::from(2u8)),
|
||||
Some(-50f32)
|
||||
);
|
||||
// should work when base value is greater than comparison value
|
||||
assert_eq!(
|
||||
percentage_difference_u256(4u8.into(), 3u8.into()),
|
||||
Some(-25f32)
|
||||
);
|
||||
// should work when comparison value is greater than base value
|
||||
assert_eq!(
|
||||
percentage_difference_u256(4u8.into(), 5u8.into()),
|
||||
Some(25f32)
|
||||
);
|
||||
// should be accurate to 7 decimal places
|
||||
let result =
|
||||
percentage_difference_u256(Uint256::from(31415926u64), Uint256::from(13371337u64))
|
||||
.expect("should get percentage");
|
||||
// result = -57.4377116
|
||||
assert!(result > -57.43772);
|
||||
assert!(result <= -57.43771);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use eth2::types::FullPayloadContents;
|
||||
use lru::LruCache;
|
||||
use parking_lot::Mutex;
|
||||
use std::num::NonZeroUsize;
|
||||
use tree_hash::TreeHash;
|
||||
use types::non_zero_usize::new_non_zero_usize;
|
||||
use types::{EthSpec, Hash256};
|
||||
|
||||
pub const DEFAULT_PAYLOAD_CACHE_SIZE: usize = 10;
|
||||
pub const DEFAULT_PAYLOAD_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(10);
|
||||
|
||||
/// A cache mapping execution payloads by tree hash roots.
|
||||
pub struct PayloadCache<T: EthSpec> {
|
||||
|
||||
@@ -335,8 +335,9 @@ pub fn serve<E: EthSpec>(
|
||||
.el
|
||||
.get_payload_by_root(&root)
|
||||
.ok_or_else(|| reject("missing payload for tx root"))?;
|
||||
let resp = ForkVersionedResponse {
|
||||
let resp: ForkVersionedResponse<_> = ForkVersionedResponse {
|
||||
version: Some(fork_name),
|
||||
metadata: Default::default(),
|
||||
data: payload,
|
||||
};
|
||||
|
||||
@@ -616,8 +617,9 @@ pub fn serve<E: EthSpec>(
|
||||
.spec
|
||||
.fork_name_at_epoch(slot.epoch(E::slots_per_epoch()));
|
||||
let signed_bid = SignedBuilderBid { message, signature };
|
||||
let resp = ForkVersionedResponse {
|
||||
let resp: ForkVersionedResponse<_> = ForkVersionedResponse {
|
||||
version: Some(fork_name),
|
||||
metadata: Default::default(),
|
||||
data: signed_bid,
|
||||
};
|
||||
let json_bid = serde_json::to_string(&resp)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::{
|
||||
test_utils::{
|
||||
MockServer, DEFAULT_BUILDER_THRESHOLD_WEI, DEFAULT_JWT_SECRET, DEFAULT_TERMINAL_BLOCK,
|
||||
DEFAULT_TERMINAL_DIFFICULTY,
|
||||
MockServer, DEFAULT_JWT_SECRET, DEFAULT_TERMINAL_BLOCK, DEFAULT_TERMINAL_DIFFICULTY,
|
||||
},
|
||||
Config, *,
|
||||
};
|
||||
@@ -30,7 +29,6 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
DEFAULT_TERMINAL_BLOCK,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()),
|
||||
spec,
|
||||
None,
|
||||
@@ -43,7 +41,6 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
terminal_block: u64,
|
||||
shanghai_time: Option<u64>,
|
||||
cancun_time: Option<u64>,
|
||||
builder_threshold: Option<u128>,
|
||||
jwt_key: Option<JwtKey>,
|
||||
spec: ChainSpec,
|
||||
kzg: Option<Kzg>,
|
||||
@@ -72,7 +69,6 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
execution_endpoints: vec![url],
|
||||
secret_files: vec![path],
|
||||
suggested_fee_recipient: Some(Address::repeat_byte(42)),
|
||||
builder_profit_threshold: builder_threshold.unwrap_or(DEFAULT_BUILDER_THRESHOLD_WEI),
|
||||
..Default::default()
|
||||
};
|
||||
let el =
|
||||
@@ -143,6 +139,7 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
builder_params,
|
||||
ForkName::Merge,
|
||||
&self.spec,
|
||||
None,
|
||||
BlockProductionVersion::FullV2,
|
||||
)
|
||||
.await
|
||||
@@ -182,6 +179,7 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
builder_params,
|
||||
ForkName::Merge,
|
||||
&self.spec,
|
||||
None,
|
||||
BlockProductionVersion::BlindedV2,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -35,7 +35,6 @@ pub use mock_execution_layer::MockExecutionLayer;
|
||||
pub const DEFAULT_TERMINAL_DIFFICULTY: u64 = 6400;
|
||||
pub const DEFAULT_TERMINAL_BLOCK: u64 = 64;
|
||||
pub const DEFAULT_JWT_SECRET: [u8; 32] = [42; 32];
|
||||
pub const DEFAULT_BUILDER_THRESHOLD_WEI: u128 = 1_000_000_000_000_000_000;
|
||||
pub const DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI: u128 = 10_000_000_000_000_000;
|
||||
pub const DEFAULT_BUILDER_PAYLOAD_VALUE_WEI: u128 = 20_000_000_000_000_000;
|
||||
pub const DEFAULT_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities {
|
||||
|
||||
Reference in New Issue
Block a user