Merge remote-tracking branch 'origin/unstable' into tree-states

This commit is contained in:
Michael Sproul
2024-01-11 13:15:06 +11:00
114 changed files with 4419 additions and 2343 deletions

View File

@@ -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.

View File

@@ -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);
}
}

View File

@@ -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> {

View File

@@ -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)

View File

@@ -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

View File

@@ -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 {