Merge branch 'eip4844' into deneb-free-blobs

This commit is contained in:
Diva M 2023-03-24 14:38:29 -05:00
commit 25a2d8f078
No known key found for this signature in database
GPG Key ID: 1BAE5E01126680FE
63 changed files with 1852 additions and 536 deletions

54
Cargo.lock generated
View File

@ -614,6 +614,7 @@ dependencies = [
"task_executor",
"tempfile",
"tokio",
"tokio-stream",
"tree_hash",
"types",
"unused_port",
@ -621,7 +622,7 @@ dependencies = [
[[package]]
name = "beacon_node"
version = "3.5.1"
version = "4.0.1-rc.0"
dependencies = [
"beacon_chain",
"clap",
@ -790,7 +791,7 @@ dependencies = [
[[package]]
name = "boot_node"
version = "3.5.1"
version = "4.0.1-rc.0"
dependencies = [
"beacon_node",
"clap",
@ -1619,16 +1620,6 @@ version = "0.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b72465f46d518f6015d9cf07f7f3013a95dd6b9c2747c3d65ae0cce43929d14f"
[[package]]
name = "delay_map"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c4d75d3abfe4830dcbf9bcb1b926954e121669f74dd1ca7aa0183b1755d83f6"
dependencies = [
"futures",
"tokio-util 0.6.10",
]
[[package]]
name = "delay_map"
version = "0.3.0"
@ -1830,15 +1821,15 @@ dependencies = [
[[package]]
name = "discv5"
version = "0.1.0"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767c0e59b3e8d65222d95df723cc2ea1da92bb0f27c563607e6f0bde064f255"
checksum = "b009a99b85b58900df46435307fc5c4c845af7e182582b1fbf869572fa9fce69"
dependencies = [
"aes 0.7.5",
"aes-gcm 0.9.4",
"arrayvec",
"delay_map 0.1.2",
"enr",
"delay_map",
"enr 0.7.0",
"fnv",
"futures",
"hashlink 0.7.0",
@ -1987,6 +1978,25 @@ name = "enr"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26fa0a0be8915790626d5759eb51fe47435a8eac92c2f212bd2da9aa7f30ea56"
dependencies = [
"base64 0.13.1",
"bs58",
"bytes",
"hex",
"k256",
"log",
"rand 0.8.5",
"rlp",
"serde",
"sha3 0.10.6",
"zeroize",
]
[[package]]
name = "enr"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "492a7e5fc2504d5fdce8e124d3e263b244a68b283cac67a69eda0cd43e0aebad"
dependencies = [
"base64 0.13.1",
"bs58",
@ -2235,7 +2245,7 @@ dependencies = [
name = "eth2_network_config"
version = "0.2.0"
dependencies = [
"enr",
"discv5",
"eth2_config",
"eth2_ssz",
"kzg",
@ -2388,7 +2398,7 @@ dependencies = [
"async-stream",
"blst",
"bs58",
"enr",
"enr 0.6.2",
"hex",
"integer-sqrt",
"multiaddr 0.14.0",
@ -3794,7 +3804,7 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "lcli"
version = "3.5.1"
version = "4.0.1-rc.0"
dependencies = [
"account_utils",
"beacon_chain",
@ -4400,7 +4410,7 @@ dependencies = [
[[package]]
name = "lighthouse"
version = "3.5.1"
version = "4.0.1-rc.0"
dependencies = [
"account_manager",
"account_utils",
@ -4450,7 +4460,7 @@ dependencies = [
name = "lighthouse_network"
version = "0.2.0"
dependencies = [
"delay_map 0.3.0",
"delay_map",
"directory",
"dirs",
"discv5",
@ -5067,7 +5077,7 @@ name = "network"
version = "0.2.0"
dependencies = [
"beacon_chain",
"delay_map 0.3.0",
"delay_map",
"derivative",
"environment",
"error-chain",

View File

@ -1,6 +1,6 @@
[package]
name = "beacon_node"
version = "3.5.1"
version = "4.0.1-rc.0"
authors = ["Paul Hauner <paul@paulhauner.com>", "Age Manning <Age@AgeManning.com"]
edition = "2021"

View File

@ -41,6 +41,7 @@ state_processing = { path = "../../consensus/state_processing" }
tree_hash = "0.4.1"
types = { path = "../../consensus/types" }
tokio = "1.14.0"
tokio-stream = "0.1.3"
eth1 = { path = "../eth1" }
futures = "0.3.7"
genesis = { path = "../genesis" }

View File

@ -0,0 +1,974 @@
use crate::{BeaconChain, BeaconChainError, BeaconChainTypes};
use execution_layer::{ExecutionLayer, ExecutionPayloadBodyV1};
use slog::{crit, debug, Logger};
use std::collections::HashMap;
use std::sync::Arc;
use store::{DatabaseBlock, ExecutionPayloadEip4844};
use task_executor::TaskExecutor;
use tokio::sync::{
mpsc::{self, UnboundedSender},
RwLock,
};
use tokio_stream::{wrappers::UnboundedReceiverStream, Stream};
use types::{
ChainSpec, EthSpec, ExecPayload, ExecutionBlockHash, ForkName, Hash256, SignedBeaconBlock,
SignedBlindedBeaconBlock, Slot,
};
use types::{
ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadHeader, ExecutionPayloadMerge,
};
#[derive(PartialEq)]
pub enum CheckEarlyAttesterCache {
Yes,
No,
}
#[derive(Debug)]
pub enum Error {
PayloadReconstruction(String),
BlocksByRangeFailure(Box<execution_layer::Error>),
RequestNotFound,
BlockResultNotFound,
}
const BLOCKS_PER_RANGE_REQUEST: u64 = 32;
// This is the same as a DatabaseBlock but the Arc allows us to avoid an unnecessary clone.
enum LoadedBeaconBlock<E: EthSpec> {
Full(Arc<SignedBeaconBlock<E>>),
Blinded(Box<SignedBlindedBeaconBlock<E>>),
}
type LoadResult<E> = Result<Option<LoadedBeaconBlock<E>>, BeaconChainError>;
type BlockResult<E> = Result<Option<Arc<SignedBeaconBlock<E>>>, BeaconChainError>;
enum RequestState<E: EthSpec> {
UnSent(Vec<BlockParts<E>>),
Sent(HashMap<Hash256, Arc<BlockResult<E>>>),
}
struct BodiesByRange<E: EthSpec> {
start: u64,
count: u64,
state: RequestState<E>,
}
// stores the components of a block for future re-construction in a small form
struct BlockParts<E: EthSpec> {
blinded_block: Box<SignedBlindedBeaconBlock<E>>,
header: Box<ExecutionPayloadHeader<E>>,
body: Option<Box<ExecutionPayloadBodyV1<E>>>,
}
impl<E: EthSpec> BlockParts<E> {
pub fn new(
blinded: Box<SignedBlindedBeaconBlock<E>>,
header: ExecutionPayloadHeader<E>,
) -> Self {
Self {
blinded_block: blinded,
header: Box::new(header),
body: None,
}
}
pub fn root(&self) -> Hash256 {
self.blinded_block.canonical_root()
}
pub fn slot(&self) -> Slot {
self.blinded_block.message().slot()
}
pub fn block_hash(&self) -> ExecutionBlockHash {
self.header.block_hash()
}
}
fn reconstruct_default_header_block<E: EthSpec>(
blinded_block: Box<SignedBlindedBeaconBlock<E>>,
header_from_block: ExecutionPayloadHeader<E>,
spec: &ChainSpec,
) -> BlockResult<E> {
let fork = blinded_block
.fork_name(spec)
.map_err(BeaconChainError::InconsistentFork)?;
let payload: ExecutionPayload<E> = match fork {
ForkName::Merge => ExecutionPayloadMerge::default().into(),
ForkName::Capella => ExecutionPayloadCapella::default().into(),
ForkName::Eip4844 => ExecutionPayloadEip4844::default().into(),
ForkName::Base | ForkName::Altair => {
return Err(Error::PayloadReconstruction(format!(
"Block with fork variant {} has execution payload",
fork
))
.into())
}
};
let header_from_payload = ExecutionPayloadHeader::from(payload.to_ref());
if header_from_payload == header_from_block {
blinded_block
.try_into_full_block(Some(payload))
.ok_or(BeaconChainError::AddPayloadLogicError)
.map(Arc::new)
.map(Some)
} else {
Err(BeaconChainError::InconsistentPayloadReconstructed {
slot: blinded_block.slot(),
exec_block_hash: header_from_block.block_hash(),
canonical_transactions_root: header_from_block.transactions_root(),
reconstructed_transactions_root: header_from_payload.transactions_root(),
})
}
}
fn reconstruct_blocks<E: EthSpec>(
block_map: &mut HashMap<Hash256, Arc<BlockResult<E>>>,
block_parts_with_bodies: HashMap<Hash256, BlockParts<E>>,
log: &Logger,
) {
for (root, block_parts) in block_parts_with_bodies {
if let Some(payload_body) = block_parts.body {
match payload_body.to_payload(block_parts.header.as_ref().clone()) {
Ok(payload) => {
let header_from_payload = ExecutionPayloadHeader::from(payload.to_ref());
if header_from_payload == *block_parts.header {
block_map.insert(
root,
Arc::new(
block_parts
.blinded_block
.try_into_full_block(Some(payload))
.ok_or(BeaconChainError::AddPayloadLogicError)
.map(Arc::new)
.map(Some),
),
);
} else {
let error = BeaconChainError::InconsistentPayloadReconstructed {
slot: block_parts.blinded_block.slot(),
exec_block_hash: block_parts.header.block_hash(),
canonical_transactions_root: block_parts.header.transactions_root(),
reconstructed_transactions_root: header_from_payload
.transactions_root(),
};
debug!(log, "Failed to reconstruct block"; "root" => ?root, "error" => ?error);
block_map.insert(root, Arc::new(Err(error)));
}
}
Err(string) => {
block_map.insert(
root,
Arc::new(Err(Error::PayloadReconstruction(string).into())),
);
}
}
} else {
block_map.insert(
root,
Arc::new(Err(BeaconChainError::BlockHashMissingFromExecutionLayer(
block_parts.block_hash(),
))),
);
}
}
}
impl<E: EthSpec> BodiesByRange<E> {
pub fn new(maybe_block_parts: Option<BlockParts<E>>) -> Self {
if let Some(block_parts) = maybe_block_parts {
Self {
start: block_parts.header.block_number(),
count: 1,
state: RequestState::UnSent(vec![block_parts]),
}
} else {
Self {
start: 0,
count: 0,
state: RequestState::UnSent(vec![]),
}
}
}
pub fn is_unsent(&self) -> bool {
matches!(self.state, RequestState::UnSent(_))
}
pub fn push_block_parts(&mut self, block_parts: BlockParts<E>) -> Result<(), BlockParts<E>> {
if self.count == BLOCKS_PER_RANGE_REQUEST {
return Err(block_parts);
}
match &mut self.state {
RequestState::Sent(_) => Err(block_parts),
RequestState::UnSent(blocks_parts_vec) => {
let block_number = block_parts.header.block_number();
if self.count == 0 {
self.start = block_number;
self.count = 1;
blocks_parts_vec.push(block_parts);
Ok(())
} else {
// need to figure out if this block fits in the request
if block_number < self.start
|| self.start + BLOCKS_PER_RANGE_REQUEST <= block_number
{
return Err(block_parts);
}
blocks_parts_vec.push(block_parts);
if self.start + self.count <= block_number {
self.count = block_number - self.start + 1;
}
Ok(())
}
}
}
}
async fn execute(&mut self, execution_layer: &ExecutionLayer<E>, log: &Logger) {
if let RequestState::UnSent(blocks_parts_ref) = &mut self.state {
let block_parts_vec = std::mem::take(blocks_parts_ref);
let mut block_map = HashMap::new();
match execution_layer
.get_payload_bodies_by_range(self.start, self.count)
.await
{
Ok(bodies) => {
let mut range_map = (self.start..(self.start + self.count))
.zip(bodies.into_iter().chain(std::iter::repeat(None)))
.collect::<HashMap<_, _>>();
let mut with_bodies = HashMap::new();
for mut block_parts in block_parts_vec {
with_bodies
// it's possible the same block is requested twice, using
// or_insert_with() skips duplicates
.entry(block_parts.root())
.or_insert_with(|| {
let block_number = block_parts.header.block_number();
block_parts.body =
range_map.remove(&block_number).flatten().map(Box::new);
block_parts
});
}
reconstruct_blocks(&mut block_map, with_bodies, log);
}
Err(e) => {
let block_result =
Arc::new(Err(Error::BlocksByRangeFailure(Box::new(e)).into()));
debug!(log, "Payload bodies by range failure"; "error" => ?block_result);
for block_parts in block_parts_vec {
block_map.insert(block_parts.root(), block_result.clone());
}
}
}
self.state = RequestState::Sent(block_map);
}
}
pub async fn get_block_result(
&mut self,
root: &Hash256,
execution_layer: &ExecutionLayer<E>,
log: &Logger,
) -> Option<Arc<BlockResult<E>>> {
self.execute(execution_layer, log).await;
if let RequestState::Sent(map) = &self.state {
return map.get(root).cloned();
}
// Shouldn't reach this point
None
}
}
#[derive(Clone)]
enum EngineRequest<E: EthSpec> {
ByRange(Arc<RwLock<BodiesByRange<E>>>),
// When we already have the data or there's an error
NoRequest(Arc<RwLock<HashMap<Hash256, Arc<BlockResult<E>>>>>),
}
impl<E: EthSpec> EngineRequest<E> {
pub fn new_by_range() -> Self {
Self::ByRange(Arc::new(RwLock::new(BodiesByRange::new(None))))
}
pub fn new_no_request() -> Self {
Self::NoRequest(Arc::new(RwLock::new(HashMap::new())))
}
pub async fn is_unsent(&self) -> bool {
match self {
Self::ByRange(bodies_by_range) => bodies_by_range.read().await.is_unsent(),
Self::NoRequest(_) => false,
}
}
pub async fn push_block_parts(&mut self, block_parts: BlockParts<E>, log: &Logger) {
match self {
Self::ByRange(bodies_by_range) => {
let mut request = bodies_by_range.write().await;
if let Err(block_parts) = request.push_block_parts(block_parts) {
drop(request);
let new_by_range = BodiesByRange::new(Some(block_parts));
*self = Self::ByRange(Arc::new(RwLock::new(new_by_range)));
}
}
Self::NoRequest(_) => {
// this should _never_ happen
crit!(
log,
"Please notify the devs";
"beacon_block_streamer" => "push_block_parts called on NoRequest Variant",
);
}
}
}
pub async fn push_block_result(
&mut self,
root: Hash256,
block_result: BlockResult<E>,
log: &Logger,
) {
// this function will only fail if something is seriously wrong
match self {
Self::ByRange(_) => {
// this should _never_ happen
crit!(
log,
"Please notify the devs";
"beacon_block_streamer" => "push_block_result called on ByRange",
);
}
Self::NoRequest(results) => {
results.write().await.insert(root, Arc::new(block_result));
}
}
}
pub async fn get_block_result(
&self,
root: &Hash256,
execution_layer: &ExecutionLayer<E>,
log: &Logger,
) -> Arc<BlockResult<E>> {
match self {
Self::ByRange(by_range) => {
by_range
.write()
.await
.get_block_result(root, execution_layer, log)
.await
}
Self::NoRequest(map) => map.read().await.get(root).cloned(),
}
.unwrap_or_else(|| {
crit!(
log,
"Please notify the devs";
"beacon_block_streamer" => "block_result not found in request",
"root" => ?root,
);
Arc::new(Err(Error::BlockResultNotFound.into()))
})
}
}
pub struct BeaconBlockStreamer<T: BeaconChainTypes> {
execution_layer: ExecutionLayer<T::EthSpec>,
check_early_attester_cache: CheckEarlyAttesterCache,
beacon_chain: Arc<BeaconChain<T>>,
}
impl<T: BeaconChainTypes> BeaconBlockStreamer<T> {
pub fn new(
beacon_chain: &Arc<BeaconChain<T>>,
check_early_attester_cache: CheckEarlyAttesterCache,
) -> Result<Self, BeaconChainError> {
let execution_layer = beacon_chain
.execution_layer
.as_ref()
.ok_or(BeaconChainError::ExecutionLayerMissing)?
.clone();
Ok(Self {
execution_layer,
check_early_attester_cache,
beacon_chain: beacon_chain.clone(),
})
}
fn check_early_attester_cache(
&self,
root: Hash256,
) -> Option<Arc<SignedBeaconBlock<T::EthSpec>>> {
if self.check_early_attester_cache == CheckEarlyAttesterCache::Yes {
self.beacon_chain.early_attester_cache.get_block(root)
} else {
None
}
}
fn load_payloads(&self, block_roots: Vec<Hash256>) -> Vec<(Hash256, LoadResult<T::EthSpec>)> {
let mut db_blocks = Vec::new();
for root in block_roots {
if let Some(cached_block) = self
.check_early_attester_cache(root)
.map(LoadedBeaconBlock::Full)
{
db_blocks.push((root, Ok(Some(cached_block))));
continue;
}
match self.beacon_chain.store.try_get_full_block(&root) {
Err(e) => db_blocks.push((root, Err(e.into()))),
Ok(opt_block) => db_blocks.push((
root,
Ok(opt_block.map(|db_block| match db_block {
DatabaseBlock::Full(block) => LoadedBeaconBlock::Full(Arc::new(block)),
DatabaseBlock::Blinded(block) => {
LoadedBeaconBlock::Blinded(Box::new(block))
}
})),
)),
}
}
db_blocks
}
/// Pre-process the loaded blocks into execution engine requests.
///
/// The purpose of this function is to separate the blocks into 2 categories:
/// 1) no_request - when we already have the full block or there's an error
/// 2) blocks_by_range - used for blinded blocks
///
/// The function returns a vector of block roots in the same order as requested
/// along with the engine request that each root corresponds to.
async fn get_requests(
&self,
payloads: Vec<(Hash256, LoadResult<T::EthSpec>)>,
) -> Vec<(Hash256, EngineRequest<T::EthSpec>)> {
let mut ordered_block_roots = Vec::new();
let mut requests = HashMap::new();
// we sort the by range blocks by slot before adding them to the
// request as it should *better* optimize the number of blocks that
// can fit in the same request
let mut by_range_blocks: Vec<BlockParts<T::EthSpec>> = vec![];
let mut no_request = EngineRequest::new_no_request();
for (root, load_result) in payloads {
// preserve the order of the requested blocks
ordered_block_roots.push(root);
let block_result = match load_result {
Err(e) => Err(e),
Ok(None) => Ok(None),
Ok(Some(LoadedBeaconBlock::Full(full_block))) => Ok(Some(full_block)),
Ok(Some(LoadedBeaconBlock::Blinded(blinded_block))) => {
match blinded_block
.message()
.execution_payload()
.map(|payload| payload.to_execution_payload_header())
{
Ok(header) => {
if header.block_hash() == ExecutionBlockHash::zero() {
reconstruct_default_header_block(
blinded_block,
header,
&self.beacon_chain.spec,
)
} else {
// Add the block to the set requiring a by-range request.
let block_parts = BlockParts::new(blinded_block, header);
by_range_blocks.push(block_parts);
continue;
}
}
Err(e) => Err(BeaconChainError::BeaconStateError(e)),
}
}
};
no_request
.push_block_result(root, block_result, &self.beacon_chain.log)
.await;
requests.insert(root, no_request.clone());
}
// Now deal with the by_range requests. Sort them in order of increasing slot
let mut by_range = EngineRequest::<T::EthSpec>::new_by_range();
by_range_blocks.sort_by_key(|block_parts| block_parts.slot());
for block_parts in by_range_blocks {
let root = block_parts.root();
by_range
.push_block_parts(block_parts, &self.beacon_chain.log)
.await;
requests.insert(root, by_range.clone());
}
let mut result = vec![];
for root in ordered_block_roots {
if let Some(request) = requests.get(&root) {
result.push((root, request.clone()))
} else {
crit!(
self.beacon_chain.log,
"Please notify the devs";
"beacon_block_streamer" => "request not found",
"root" => ?root,
);
no_request
.push_block_result(
root,
Err(Error::RequestNotFound.into()),
&self.beacon_chain.log,
)
.await;
result.push((root, no_request.clone()));
}
}
result
}
// used when the execution engine doesn't support the payload bodies methods
async fn stream_blocks_fallback(
&self,
block_roots: Vec<Hash256>,
sender: UnboundedSender<(Hash256, Arc<BlockResult<T::EthSpec>>)>,
) {
debug!(
self.beacon_chain.log,
"Using slower fallback method of eth_getBlockByHash()"
);
for root in block_roots {
let cached_block = self.check_early_attester_cache(root);
let block_result = if cached_block.is_some() {
Ok(cached_block)
} else {
self.beacon_chain
.get_block(&root)
.await
.map(|opt_block| opt_block.map(Arc::new))
};
if sender.send((root, Arc::new(block_result))).is_err() {
break;
}
}
}
async fn stream_blocks(
&self,
block_roots: Vec<Hash256>,
sender: UnboundedSender<(Hash256, Arc<BlockResult<T::EthSpec>>)>,
) {
let n_roots = block_roots.len();
let mut n_success = 0usize;
let mut n_sent = 0usize;
let mut engine_requests = 0usize;
let payloads = self.load_payloads(block_roots);
let requests = self.get_requests(payloads).await;
for (root, request) in requests {
if request.is_unsent().await {
engine_requests += 1;
}
let result = request
.get_block_result(&root, &self.execution_layer, &self.beacon_chain.log)
.await;
let successful = result
.as_ref()
.as_ref()
.map(|opt| opt.is_some())
.unwrap_or(false);
if sender.send((root, result)).is_err() {
break;
} else {
n_sent += 1;
if successful {
n_success += 1;
}
}
}
debug!(
self.beacon_chain.log,
"BeaconBlockStreamer finished";
"requested blocks" => n_roots,
"sent" => n_sent,
"succeeded" => n_success,
"failed" => (n_sent - n_success),
"engine requests" => engine_requests,
);
}
pub async fn stream(
self,
block_roots: Vec<Hash256>,
sender: UnboundedSender<(Hash256, Arc<BlockResult<T::EthSpec>>)>,
) {
match self
.execution_layer
.get_engine_capabilities(None)
.await
.map_err(Box::new)
.map_err(BeaconChainError::EngineGetCapabilititesFailed)
{
Ok(engine_capabilities) => {
if engine_capabilities.get_payload_bodies_by_range_v1 {
self.stream_blocks(block_roots, sender).await;
} else {
// use the fallback method
self.stream_blocks_fallback(block_roots, sender).await;
}
}
Err(e) => {
send_errors(block_roots, sender, e).await;
}
}
}
pub fn launch_stream(
self,
block_roots: Vec<Hash256>,
executor: &TaskExecutor,
) -> impl Stream<Item = (Hash256, Arc<BlockResult<T::EthSpec>>)> {
let (block_tx, block_rx) = mpsc::unbounded_channel();
debug!(
self.beacon_chain.log,
"Launching a BeaconBlockStreamer";
"blocks" => block_roots.len(),
);
executor.spawn(self.stream(block_roots, block_tx), "get_blocks_sender");
UnboundedReceiverStream::new(block_rx)
}
}
async fn send_errors<E: EthSpec>(
block_roots: Vec<Hash256>,
sender: UnboundedSender<(Hash256, Arc<BlockResult<E>>)>,
beacon_chain_error: BeaconChainError,
) {
let result = Arc::new(Err(beacon_chain_error));
for root in block_roots {
if sender.send((root, result.clone())).is_err() {
break;
}
}
}
impl From<Error> for BeaconChainError {
fn from(value: Error) -> Self {
BeaconChainError::BlockStreamerError(value)
}
}
#[cfg(test)]
mod tests {
use crate::beacon_block_streamer::{BeaconBlockStreamer, CheckEarlyAttesterCache};
use crate::test_utils::{test_spec, BeaconChainHarness, EphemeralHarnessType};
use execution_layer::test_utils::{Block, DEFAULT_ENGINE_CAPABILITIES};
use execution_layer::EngineCapabilities;
use lazy_static::lazy_static;
use std::time::Duration;
use tokio::sync::mpsc;
use types::{ChainSpec, Epoch, EthSpec, Hash256, Keypair, MinimalEthSpec, Slot};
const VALIDATOR_COUNT: usize = 48;
lazy_static! {
/// A cached set of keys.
static ref KEYPAIRS: Vec<Keypair> = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
}
fn get_harness(
validator_count: usize,
spec: ChainSpec,
) -> BeaconChainHarness<EphemeralHarnessType<MinimalEthSpec>> {
let harness = BeaconChainHarness::builder(MinimalEthSpec)
.spec(spec)
.keypairs(KEYPAIRS[0..validator_count].to_vec())
.logger(logging::test_logger())
.fresh_ephemeral_store()
.mock_execution_layer()
.build();
harness.advance_slot();
harness
}
#[tokio::test]
async fn check_all_blocks_from_altair_to_capella() {
let slots_per_epoch = MinimalEthSpec::slots_per_epoch() as usize;
let num_epochs = 8;
let bellatrix_fork_epoch = 2usize;
let capella_fork_epoch = 4usize;
let num_blocks_produced = num_epochs * slots_per_epoch;
let mut spec = test_spec::<MinimalEthSpec>();
spec.altair_fork_epoch = Some(Epoch::new(0));
spec.bellatrix_fork_epoch = Some(Epoch::new(bellatrix_fork_epoch as u64));
spec.capella_fork_epoch = Some(Epoch::new(capella_fork_epoch as u64));
let harness = get_harness(VALIDATOR_COUNT, spec);
// go to bellatrix fork
harness
.extend_slots(bellatrix_fork_epoch * slots_per_epoch)
.await;
// extend half an epoch
harness.extend_slots(slots_per_epoch / 2).await;
// trigger merge
harness
.execution_block_generator()
.move_to_terminal_block()
.expect("should move to terminal block");
let timestamp = harness.get_timestamp_at_slot() + harness.spec.seconds_per_slot;
harness
.execution_block_generator()
.modify_last_block(|block| {
if let Block::PoW(terminal_block) = block {
terminal_block.timestamp = timestamp;
}
});
// finish out merge epoch
harness.extend_slots(slots_per_epoch / 2).await;
// finish rest of epochs
harness
.extend_slots((num_epochs - 1 - bellatrix_fork_epoch) * slots_per_epoch)
.await;
let head = harness.chain.head_snapshot();
let state = &head.beacon_state;
assert_eq!(
state.slot(),
Slot::new(num_blocks_produced as u64),
"head should be at the current slot"
);
assert_eq!(
state.current_epoch(),
num_blocks_produced as u64 / MinimalEthSpec::slots_per_epoch(),
"head should be at the expected epoch"
);
assert_eq!(
state.current_justified_checkpoint().epoch,
state.current_epoch() - 1,
"the head should be justified one behind the current epoch"
);
assert_eq!(
state.finalized_checkpoint().epoch,
state.current_epoch() - 2,
"the head should be finalized two behind the current epoch"
);
let block_roots: Vec<Hash256> = harness
.chain
.forwards_iter_block_roots(Slot::new(0))
.expect("should get iter")
.map(Result::unwrap)
.map(|(root, _)| root)
.collect();
let mut expected_blocks = vec![];
// get all blocks the old fashioned way
for root in &block_roots {
let block = harness
.chain
.get_block(root)
.await
.expect("should get block")
.expect("block should exist");
expected_blocks.push(block);
}
for epoch in 0..num_epochs {
let start = epoch * slots_per_epoch;
let mut epoch_roots = vec![Hash256::zero(); slots_per_epoch];
epoch_roots[..].clone_from_slice(&block_roots[start..(start + slots_per_epoch)]);
let streamer = BeaconBlockStreamer::new(&harness.chain, CheckEarlyAttesterCache::No)
.expect("should create streamer");
let (block_tx, mut block_rx) = mpsc::unbounded_channel();
streamer.stream(epoch_roots.clone(), block_tx).await;
for (i, expected_root) in epoch_roots.into_iter().enumerate() {
let (found_root, found_block_result) =
block_rx.recv().await.expect("should get block");
assert_eq!(
found_root, expected_root,
"expected block root should match"
);
match found_block_result.as_ref() {
Ok(maybe_block) => {
let found_block = maybe_block.clone().expect("should have a block");
let expected_block = expected_blocks
.get(start + i)
.expect("should get expected block");
assert_eq!(
found_block.as_ref(),
expected_block,
"expected block should match found block"
);
}
Err(e) => panic!("Error retrieving block {}: {:?}", expected_root, e),
}
}
}
}
#[tokio::test]
async fn check_fallback_altair_to_capella() {
let slots_per_epoch = MinimalEthSpec::slots_per_epoch() as usize;
let num_epochs = 8;
let bellatrix_fork_epoch = 2usize;
let capella_fork_epoch = 4usize;
let num_blocks_produced = num_epochs * slots_per_epoch;
let mut spec = test_spec::<MinimalEthSpec>();
spec.altair_fork_epoch = Some(Epoch::new(0));
spec.bellatrix_fork_epoch = Some(Epoch::new(bellatrix_fork_epoch as u64));
spec.capella_fork_epoch = Some(Epoch::new(capella_fork_epoch as u64));
let harness = get_harness(VALIDATOR_COUNT, spec);
// modify execution engine so it doesn't support engine_payloadBodiesBy* methods
let mock_execution_layer = harness.mock_execution_layer.as_ref().unwrap();
mock_execution_layer
.server
.set_engine_capabilities(EngineCapabilities {
get_payload_bodies_by_hash_v1: false,
get_payload_bodies_by_range_v1: false,
..DEFAULT_ENGINE_CAPABILITIES
});
// refresh capabilities cache
harness
.chain
.execution_layer
.as_ref()
.unwrap()
.get_engine_capabilities(Some(Duration::ZERO))
.await
.unwrap();
// go to bellatrix fork
harness
.extend_slots(bellatrix_fork_epoch * slots_per_epoch)
.await;
// extend half an epoch
harness.extend_slots(slots_per_epoch / 2).await;
// trigger merge
harness
.execution_block_generator()
.move_to_terminal_block()
.expect("should move to terminal block");
let timestamp = harness.get_timestamp_at_slot() + harness.spec.seconds_per_slot;
harness
.execution_block_generator()
.modify_last_block(|block| {
if let Block::PoW(terminal_block) = block {
terminal_block.timestamp = timestamp;
}
});
// finish out merge epoch
harness.extend_slots(slots_per_epoch / 2).await;
// finish rest of epochs
harness
.extend_slots((num_epochs - 1 - bellatrix_fork_epoch) * slots_per_epoch)
.await;
let head = harness.chain.head_snapshot();
let state = &head.beacon_state;
assert_eq!(
state.slot(),
Slot::new(num_blocks_produced as u64),
"head should be at the current slot"
);
assert_eq!(
state.current_epoch(),
num_blocks_produced as u64 / MinimalEthSpec::slots_per_epoch(),
"head should be at the expected epoch"
);
assert_eq!(
state.current_justified_checkpoint().epoch,
state.current_epoch() - 1,
"the head should be justified one behind the current epoch"
);
assert_eq!(
state.finalized_checkpoint().epoch,
state.current_epoch() - 2,
"the head should be finalized two behind the current epoch"
);
let block_roots: Vec<Hash256> = harness
.chain
.forwards_iter_block_roots(Slot::new(0))
.expect("should get iter")
.map(Result::unwrap)
.map(|(root, _)| root)
.collect();
let mut expected_blocks = vec![];
// get all blocks the old fashioned way
for root in &block_roots {
let block = harness
.chain
.get_block(root)
.await
.expect("should get block")
.expect("block should exist");
expected_blocks.push(block);
}
for epoch in 0..num_epochs {
let start = epoch * slots_per_epoch;
let mut epoch_roots = vec![Hash256::zero(); slots_per_epoch];
epoch_roots[..].clone_from_slice(&block_roots[start..(start + slots_per_epoch)]);
let streamer = BeaconBlockStreamer::new(&harness.chain, CheckEarlyAttesterCache::No)
.expect("should create streamer");
let (block_tx, mut block_rx) = mpsc::unbounded_channel();
streamer.stream(epoch_roots.clone(), block_tx).await;
for (i, expected_root) in epoch_roots.into_iter().enumerate() {
let (found_root, found_block_result) =
block_rx.recv().await.expect("should get block");
assert_eq!(
found_root, expected_root,
"expected block root should match"
);
match found_block_result.as_ref() {
Ok(maybe_block) => {
let found_block = maybe_block.clone().expect("should have a block");
let expected_block = expected_blocks
.get(start + i)
.expect("should get expected block");
assert_eq!(
found_block.as_ref(),
expected_block,
"expected block should match found block"
);
}
Err(e) => panic!("Error retrieving block {}: {:?}", expected_root, e),
}
}
}
}
}

View File

@ -4,6 +4,7 @@ use crate::attestation_verification::{
VerifiedUnaggregatedAttestation,
};
use crate::attester_cache::{AttesterCache, AttesterCacheKey};
use crate::beacon_block_streamer::{BeaconBlockStreamer, CheckEarlyAttesterCache};
use crate::beacon_proposer_cache::compute_proposer_duties_from_head;
use crate::beacon_proposer_cache::BeaconProposerCache;
use crate::blob_cache::BlobCache;
@ -76,7 +77,7 @@ use itertools::Itertools;
use kzg::Kzg;
use operation_pool::{AttestationRef, OperationPool, PersistedOperationPool, ReceivedPreCapella};
use parking_lot::{Mutex, RwLock};
use proto_array::{CountUnrealizedFull, DoNotReOrg, ProposerHeadError};
use proto_array::{DoNotReOrg, ProposerHeadError};
use safe_arith::SafeArith;
use slasher::Slasher;
use slog::{crit, debug, error, info, trace, warn, Logger};
@ -106,6 +107,7 @@ use store::{
DatabaseBlock, Error as DBError, HotColdDB, KeyValueStore, KeyValueStoreOp, StoreItem, StoreOp,
};
use task_executor::{ShutdownReason, TaskExecutor};
use tokio_stream::Stream;
use tree_hash::TreeHash;
use types::beacon_state::CloneConfig;
use types::blobs_sidecar::KzgCommitments;
@ -485,7 +487,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn load_fork_choice(
store: BeaconStore<T>,
reset_payload_statuses: ResetPayloadStatuses,
count_unrealized_full: CountUnrealizedFull,
spec: &ChainSpec,
log: &Logger,
) -> Result<Option<BeaconForkChoice<T>>, Error> {
@ -502,7 +503,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
persisted_fork_choice.fork_choice,
reset_payload_statuses,
fc_store,
count_unrealized_full,
spec,
log,
)?))
@ -949,14 +949,42 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// ## Errors
///
/// May return a database error.
pub async fn get_block_checking_early_attester_cache(
&self,
block_root: &Hash256,
) -> Result<Option<Arc<SignedBeaconBlock<T::EthSpec>>>, Error> {
if let Some(block) = self.early_attester_cache.get_block(*block_root) {
return Ok(Some(block));
}
Ok(self.get_block(block_root).await?.map(Arc::new))
pub fn get_blocks_checking_early_attester_cache(
self: &Arc<Self>,
block_roots: Vec<Hash256>,
executor: &TaskExecutor,
) -> Result<
impl Stream<
Item = (
Hash256,
Arc<Result<Option<Arc<SignedBeaconBlock<T::EthSpec>>>, Error>>,
),
>,
Error,
> {
Ok(
BeaconBlockStreamer::<T>::new(self, CheckEarlyAttesterCache::Yes)?
.launch_stream(block_roots, executor),
)
}
pub fn get_blocks(
self: &Arc<Self>,
block_roots: Vec<Hash256>,
executor: &TaskExecutor,
) -> Result<
impl Stream<
Item = (
Hash256,
Arc<Result<Option<Arc<SignedBeaconBlock<T::EthSpec>>>, Error>>,
),
>,
Error,
> {
Ok(
BeaconBlockStreamer::<T>::new(self, CheckEarlyAttesterCache::No)?
.launch_stream(block_roots, executor),
)
}
pub async fn get_blobs_checking_early_attester_cache(
@ -1944,7 +1972,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
self.slot()?,
verified.indexed_attestation(),
AttestationFromBlock::False,
&self.spec,
)
.map_err(Into::into)
}
@ -2911,7 +2938,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
&state,
payload_verification_status,
&self.spec,
count_unrealized.and(self.config.count_unrealized.into()),
count_unrealized,
)
.map_err(|e| BlockError::BeaconChainError(e.into()))?;
}
@ -3054,7 +3081,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
ResetPayloadStatuses::always_reset_conditionally(
self.config.always_reset_payload_statuses,
),
self.config.count_unrealized_full,
&self.store,
&self.spec,
&self.log,

View File

@ -20,6 +20,14 @@ use types::{
Hash256, Slot,
};
/// Ensure this justified checkpoint has an epoch of 0 so that it is never
/// greater than the justified checkpoint and enshrined as the actual justified
/// checkpoint.
const JUNK_BEST_JUSTIFIED_CHECKPOINT: Checkpoint = Checkpoint {
epoch: Epoch::new(0),
root: Hash256::repeat_byte(0),
};
#[derive(Debug)]
pub enum Error {
UnableToReadSlot,
@ -144,7 +152,6 @@ pub struct BeaconForkChoiceStore<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<
finalized_checkpoint: Checkpoint,
justified_checkpoint: Checkpoint,
justified_balances: JustifiedBalances,
best_justified_checkpoint: Checkpoint,
unrealized_justified_checkpoint: Checkpoint,
unrealized_finalized_checkpoint: Checkpoint,
proposer_boost_root: Hash256,
@ -194,7 +201,6 @@ where
justified_checkpoint,
justified_balances,
finalized_checkpoint,
best_justified_checkpoint: justified_checkpoint,
unrealized_justified_checkpoint: justified_checkpoint,
unrealized_finalized_checkpoint: finalized_checkpoint,
proposer_boost_root: Hash256::zero(),
@ -212,7 +218,7 @@ where
finalized_checkpoint: self.finalized_checkpoint,
justified_checkpoint: self.justified_checkpoint,
justified_balances: self.justified_balances.effective_balances.clone(),
best_justified_checkpoint: self.best_justified_checkpoint,
best_justified_checkpoint: JUNK_BEST_JUSTIFIED_CHECKPOINT,
unrealized_justified_checkpoint: self.unrealized_justified_checkpoint,
unrealized_finalized_checkpoint: self.unrealized_finalized_checkpoint,
proposer_boost_root: self.proposer_boost_root,
@ -234,7 +240,6 @@ where
finalized_checkpoint: persisted.finalized_checkpoint,
justified_checkpoint: persisted.justified_checkpoint,
justified_balances,
best_justified_checkpoint: persisted.best_justified_checkpoint,
unrealized_justified_checkpoint: persisted.unrealized_justified_checkpoint,
unrealized_finalized_checkpoint: persisted.unrealized_finalized_checkpoint,
proposer_boost_root: persisted.proposer_boost_root,
@ -277,10 +282,6 @@ where
&self.justified_balances
}
fn best_justified_checkpoint(&self) -> &Checkpoint {
&self.best_justified_checkpoint
}
fn finalized_checkpoint(&self) -> &Checkpoint {
&self.finalized_checkpoint
}
@ -333,10 +334,6 @@ where
Ok(())
}
fn set_best_justified_checkpoint(&mut self, checkpoint: Checkpoint) {
self.best_justified_checkpoint = checkpoint
}
fn set_unrealized_justified_checkpoint(&mut self, checkpoint: Checkpoint) {
self.unrealized_justified_checkpoint = checkpoint;
}

View File

@ -1550,7 +1550,6 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
current_slot,
indexed_attestation,
AttestationFromBlock::True,
&chain.spec,
) {
Ok(()) => Ok(()),
// Ignore invalid attestations whilst importing attestations from a block. The

View File

@ -19,7 +19,7 @@ use crate::{
};
use eth1::Config as Eth1Config;
use execution_layer::ExecutionLayer;
use fork_choice::{ForkChoice, ResetPayloadStatuses};
use fork_choice::{CountUnrealized, ForkChoice, ResetPayloadStatuses};
use futures::channel::mpsc::Sender;
use kzg::{Kzg, TrustedSetup};
use operation_pool::{OperationPool, PersistedOperationPool};
@ -269,7 +269,6 @@ where
ResetPayloadStatuses::always_reset_conditionally(
self.chain_config.always_reset_payload_statuses,
),
self.chain_config.count_unrealized_full,
&self.spec,
log,
)
@ -388,7 +387,6 @@ where
&genesis.beacon_block,
&genesis.beacon_state,
current_slot,
self.chain_config.count_unrealized_full,
&self.spec,
)
.map_err(|e| format!("Unable to initialize ForkChoice: {:?}", e))?;
@ -507,7 +505,6 @@ where
&snapshot.beacon_block,
&snapshot.beacon_state,
current_slot,
self.chain_config.count_unrealized_full,
&self.spec,
)
.map_err(|e| format!("Unable to initialize ForkChoice: {:?}", e))?;
@ -698,8 +695,7 @@ where
store.clone(),
Some(current_slot),
&self.spec,
self.chain_config.count_unrealized.into(),
self.chain_config.count_unrealized_full,
CountUnrealized::True,
)?;
}
@ -782,6 +778,7 @@ where
let genesis_time = head_snapshot.beacon_state.genesis_time();
let head_for_snapshot_cache = head_snapshot.clone();
let canonical_head = CanonicalHead::new(fork_choice, Arc::new(head_snapshot));
let shuffling_cache_size = self.chain_config.shuffling_cache_size;
let beacon_chain = BeaconChain {
spec: self.spec,
@ -835,7 +832,7 @@ where
DEFAULT_SNAPSHOT_CACHE_SIZE,
head_for_snapshot_cache,
)),
shuffling_cache: TimeoutRwLock::new(ShufflingCache::new()),
shuffling_cache: TimeoutRwLock::new(ShufflingCache::new(shuffling_cache_size)),
eth1_finalization_cache: TimeoutRwLock::new(Eth1FinalizationCache::new(log.clone())),
beacon_proposer_cache: <_>::default(),
block_times_cache: <_>::default(),

View File

@ -45,8 +45,7 @@ use crate::{
};
use eth2::types::{EventKind, SseChainReorg, SseFinalizedCheckpoint, SseHead, SseLateHead};
use fork_choice::{
CountUnrealizedFull, ExecutionStatus, ForkChoiceView, ForkchoiceUpdateParameters, ProtoBlock,
ResetPayloadStatuses,
ExecutionStatus, ForkChoiceView, ForkchoiceUpdateParameters, ProtoBlock, ResetPayloadStatuses,
};
use itertools::process_results;
use parking_lot::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
@ -285,19 +284,13 @@ impl<T: BeaconChainTypes> CanonicalHead<T> {
// defensive programming.
mut fork_choice_write_lock: RwLockWriteGuard<BeaconForkChoice<T>>,
reset_payload_statuses: ResetPayloadStatuses,
count_unrealized_full: CountUnrealizedFull,
store: &BeaconStore<T>,
spec: &ChainSpec,
log: &Logger,
) -> Result<(), Error> {
let fork_choice = <BeaconChain<T>>::load_fork_choice(
store.clone(),
reset_payload_statuses,
count_unrealized_full,
spec,
log,
)?
.ok_or(Error::MissingPersistedForkChoice)?;
let fork_choice =
<BeaconChain<T>>::load_fork_choice(store.clone(), reset_payload_statuses, spec, log)?
.ok_or(Error::MissingPersistedForkChoice)?;
let fork_choice_view = fork_choice.cached_fork_choice_view();
let beacon_block_root = fork_choice_view.head_block_root;
let beacon_block = store

View File

@ -1,4 +1,4 @@
pub use proto_array::{CountUnrealizedFull, ReOrgThreshold};
pub use proto_array::ReOrgThreshold;
use serde_derive::{Deserialize, Serialize};
use std::time::Duration;
use types::{Checkpoint, Epoch};
@ -48,16 +48,11 @@ pub struct ChainConfig {
pub builder_fallback_epochs_since_finalization: usize,
/// Whether any chain health checks should be considered when deciding whether to use the builder API.
pub builder_fallback_disable_checks: bool,
/// When set to `true`, weigh the "unrealized" FFG progression when choosing a head in fork
/// choice.
pub count_unrealized: bool,
/// When set to `true`, forget any valid/invalid/optimistic statuses in fork choice during start
/// up.
pub always_reset_payload_statuses: bool,
/// Whether to apply paranoid checks to blocks proposed by this beacon node.
pub paranoid_block_proposal: bool,
/// Whether to strictly count unrealized justified votes.
pub count_unrealized_full: CountUnrealizedFull,
/// Optionally set timeout for calls to checkpoint sync endpoint.
pub checkpoint_sync_url_timeout: u64,
/// The offset before the start of a proposal slot at which payload attributes should be sent.
@ -67,6 +62,8 @@ pub struct ChainConfig {
pub prepare_payload_lookahead: Duration,
/// Use EL-free optimistic sync for the finalized part of the chain.
pub optimistic_finalized_sync: bool,
/// The size of the shuffling cache,
pub shuffling_cache_size: usize,
/// Whether to send payload attributes every slot, regardless of connected proposers.
///
/// This is useful for block builders and testing.
@ -89,14 +86,13 @@ impl Default for ChainConfig {
builder_fallback_skips_per_epoch: 8,
builder_fallback_epochs_since_finalization: 3,
builder_fallback_disable_checks: false,
count_unrealized: true,
always_reset_payload_statuses: false,
paranoid_block_proposal: false,
count_unrealized_full: CountUnrealizedFull::default(),
checkpoint_sync_url_timeout: 60,
prepare_payload_lookahead: Duration::from_secs(4),
// This value isn't actually read except in tests.
optimistic_finalized_sync: true,
shuffling_cache_size: crate::shuffling_cache::DEFAULT_CACHE_SIZE,
always_prepare_payload: false,
}
}

View File

@ -1,4 +1,5 @@
use crate::attester_cache::Error as AttesterCacheError;
use crate::beacon_block_streamer::Error as BlockStreamerError;
use crate::beacon_chain::ForkChoiceError;
use crate::beacon_fork_choice_store::Error as ForkChoiceStoreError;
use crate::eth1_chain::Error as Eth1ChainError;
@ -143,6 +144,7 @@ pub enum BeaconChainError {
ExecutionLayerMissing,
BlockVariantLacksExecutionPayload(Hash256),
ExecutionLayerErrorPayloadReconstruction(ExecutionBlockHash, Box<execution_layer::Error>),
EngineGetCapabilititesFailed(Box<execution_layer::Error>),
BlockHashMissingFromExecutionLayer(ExecutionBlockHash),
InconsistentPayloadReconstructed {
slot: Slot,
@ -150,6 +152,7 @@ pub enum BeaconChainError {
canonical_transactions_root: Hash256,
reconstructed_transactions_root: Hash256,
},
BlockStreamerError(BlockStreamerError),
AddPayloadLogicError,
ExecutionForkChoiceUpdateFailed(execution_layer::Error),
PrepareProposerFailed(BlockProcessingError),

View File

@ -1,7 +1,6 @@
use crate::{BeaconForkChoiceStore, BeaconSnapshot};
use fork_choice::{CountUnrealized, ForkChoice, PayloadVerificationStatus};
use itertools::process_results;
use proto_array::CountUnrealizedFull;
use slog::{info, warn, Logger};
use state_processing::state_advance::complete_state_advance;
use state_processing::{
@ -102,7 +101,6 @@ pub fn reset_fork_choice_to_finalization<E: EthSpec, Hot: ItemStore<E>, Cold: It
current_slot: Option<Slot>,
spec: &ChainSpec,
count_unrealized_config: CountUnrealized,
count_unrealized_full_config: CountUnrealizedFull,
) -> Result<ForkChoice<BeaconForkChoiceStore<E, Hot, Cold>, E>, String> {
// Fetch finalized block.
let finalized_checkpoint = head_state.finalized_checkpoint();
@ -156,7 +154,6 @@ pub fn reset_fork_choice_to_finalization<E: EthSpec, Hot: ItemStore<E>, Cold: It
&finalized_snapshot.beacon_block,
&finalized_snapshot.beacon_state,
current_slot,
count_unrealized_full_config,
spec,
)
.map_err(|e| format!("Unable to reset fork choice for revert: {:?}", e))?;

View File

@ -2,6 +2,7 @@ pub mod attestation_rewards;
pub mod attestation_verification;
mod attester_cache;
pub mod beacon_block_reward;
mod beacon_block_streamer;
mod beacon_chain;
mod beacon_fork_choice_store;
pub mod beacon_proposer_cache;
@ -42,7 +43,7 @@ mod persisted_fork_choice;
mod pre_finalization_cache;
pub mod proposer_prep_service;
pub mod schema_change;
mod shuffling_cache;
pub mod shuffling_cache;
mod snapshot_cache;
pub mod state_advance_timer;
pub mod sync_committee_rewards;
@ -59,7 +60,7 @@ pub use self::beacon_chain::{
INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON, MAXIMUM_GOSSIP_CLOCK_DISPARITY,
};
pub use self::beacon_snapshot::BeaconSnapshot;
pub use self::chain_config::{ChainConfig, CountUnrealizedFull};
pub use self::chain_config::ChainConfig;
pub use self::errors::{BeaconChainError, BlockProductionError};
pub use self::historical_blocks::HistoricalBlockError;
pub use attestation_verification::Error as AttestationError;

View File

@ -3,6 +3,7 @@ mod migration_schema_v12;
mod migration_schema_v13;
mod migration_schema_v14;
mod migration_schema_v15;
mod migration_schema_v16;
use crate::beacon_chain::{BeaconChainTypes, ETH1_CACHE_DB_KEY};
use crate::eth1_chain::SszEth1;
@ -132,6 +133,14 @@ pub fn migrate_schema<T: BeaconChainTypes>(
let ops = migration_schema_v15::downgrade_from_v15::<T>(db.clone(), log)?;
db.store_schema_version_atomically(to, ops)
}
(SchemaVersion(15), SchemaVersion(16)) => {
let ops = migration_schema_v16::upgrade_to_v16::<T>(db.clone(), log)?;
db.store_schema_version_atomically(to, ops)
}
(SchemaVersion(16), SchemaVersion(15)) => {
let ops = migration_schema_v16::downgrade_from_v16::<T>(db.clone(), log)?;
db.store_schema_version_atomically(to, ops)
}
// Anything else is an error.
(_, _) => Err(HotColdDBError::UnsupportedSchemaVersion {
target_version: to,

View File

@ -0,0 +1,46 @@
use crate::beacon_chain::{BeaconChainTypes, FORK_CHOICE_DB_KEY};
use crate::persisted_fork_choice::PersistedForkChoiceV11;
use slog::{debug, Logger};
use std::sync::Arc;
use store::{Error, HotColdDB, KeyValueStoreOp, StoreItem};
pub fn upgrade_to_v16<T: BeaconChainTypes>(
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
log: Logger,
) -> Result<Vec<KeyValueStoreOp>, Error> {
drop_balances_cache::<T>(db, log)
}
pub fn downgrade_from_v16<T: BeaconChainTypes>(
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
log: Logger,
) -> Result<Vec<KeyValueStoreOp>, Error> {
drop_balances_cache::<T>(db, log)
}
/// Drop the balances cache from the fork choice store.
///
/// There aren't any type-level changes in this schema migration, however the
/// way that we compute the `JustifiedBalances` has changed due to:
/// https://github.com/sigp/lighthouse/pull/3962
pub fn drop_balances_cache<T: BeaconChainTypes>(
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
log: Logger,
) -> Result<Vec<KeyValueStoreOp>, Error> {
let mut persisted_fork_choice = db
.get_item::<PersistedForkChoiceV11>(&FORK_CHOICE_DB_KEY)?
.ok_or_else(|| Error::SchemaMigrationError("fork choice missing from database".into()))?;
debug!(
log,
"Dropping fork choice balances cache";
"item_count" => persisted_fork_choice.fork_choice_store.balances_cache.items.len()
);
// Drop all items in the balances cache.
persisted_fork_choice.fork_choice_store.balances_cache = <_>::default();
let kv_op = persisted_fork_choice.as_kv_store_op(FORK_CHOICE_DB_KEY);
Ok(vec![kv_op])
}

View File

@ -9,7 +9,7 @@ use types::{beacon_state::CommitteeCache, AttestationShufflingId, Epoch, Hash256
/// Each entry should be `8 + 800,000 = 800,008` bytes in size with 100k validators. (8-byte hash +
/// 100k indices). Therefore, this cache should be approx `16 * 800,008 = 12.8 MB`. (Note: this
/// ignores a few extra bytes in the caches that should be insignificant compared to the indices).
const CACHE_SIZE: usize = 16;
pub const DEFAULT_CACHE_SIZE: usize = 16;
/// The maximum number of concurrent committee cache "promises" that can be issued. In effect, this
/// limits the number of concurrent states that can be loaded into memory for the committee cache.
@ -54,9 +54,9 @@ pub struct ShufflingCache {
}
impl ShufflingCache {
pub fn new() -> Self {
pub fn new(cache_size: usize) -> Self {
Self {
cache: LruCache::new(CACHE_SIZE),
cache: LruCache::new(cache_size),
}
}
@ -172,7 +172,7 @@ impl ToArcCommitteeCache for Arc<CommitteeCache> {
impl Default for ShufflingCache {
fn default() -> Self {
Self::new()
Self::new(DEFAULT_CACHE_SIZE)
}
}
@ -249,7 +249,7 @@ mod test {
fn resolved_promise() {
let (committee_a, _) = committee_caches();
let id_a = shuffling_id(1);
let mut cache = ShufflingCache::new();
let mut cache = ShufflingCache::default();
// Create a promise.
let sender = cache.create_promise(id_a.clone()).unwrap();
@ -276,7 +276,7 @@ mod test {
#[test]
fn unresolved_promise() {
let id_a = shuffling_id(1);
let mut cache = ShufflingCache::new();
let mut cache = ShufflingCache::default();
// Create a promise.
let sender = cache.create_promise(id_a.clone()).unwrap();
@ -301,7 +301,7 @@ mod test {
fn two_promises() {
let (committee_a, committee_b) = committee_caches();
let (id_a, id_b) = (shuffling_id(1), shuffling_id(2));
let mut cache = ShufflingCache::new();
let mut cache = ShufflingCache::default();
// Create promise A.
let sender_a = cache.create_promise(id_a.clone()).unwrap();
@ -355,7 +355,7 @@ mod test {
#[test]
fn too_many_promises() {
let mut cache = ShufflingCache::new();
let mut cache = ShufflingCache::default();
for i in 0..MAX_CONCURRENT_PROMISES {
cache.create_promise(shuffling_id(i as u64)).unwrap();

View File

@ -500,7 +500,7 @@ async fn unaggregated_attestations_added_to_fork_choice_some_none() {
// Move forward a slot so all queued attestations can be processed.
harness.advance_slot();
fork_choice
.update_time(harness.chain.slot().unwrap(), &harness.chain.spec)
.update_time(harness.chain.slot().unwrap())
.unwrap();
let validator_slots: Vec<(usize, Slot)> = (0..VALIDATOR_COUNT)
@ -614,7 +614,7 @@ async fn unaggregated_attestations_added_to_fork_choice_all_updated() {
// Move forward a slot so all queued attestations can be processed.
harness.advance_slot();
fork_choice
.update_time(harness.chain.slot().unwrap(), &harness.chain.spec)
.update_time(harness.chain.slot().unwrap())
.unwrap();
let validators: Vec<usize> = (0..VALIDATOR_COUNT).collect();

View File

@ -1,7 +1,8 @@
use crate::engines::ForkchoiceState;
use crate::http::{
ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1, ENGINE_FORKCHOICE_UPDATED_V1,
ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2,
ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1,
ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1, ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2,
ENGINE_GET_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V1, ENGINE_NEW_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V3,
};
use crate::BlobTxConversionError;
@ -17,7 +18,8 @@ use strum::IntoStaticStr;
use superstruct::superstruct;
pub use types::{
Address, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadHeader,
ExecutionPayloadRef, FixedVector, ForkName, Hash256, Uint256, VariableList, Withdrawal,
ExecutionPayloadRef, FixedVector, ForkName, Hash256, Transactions, Uint256, VariableList,
Withdrawal, Withdrawals,
};
use types::{ExecutionPayloadCapella, ExecutionPayloadEip4844, ExecutionPayloadMerge};
@ -420,6 +422,99 @@ impl<T: EthSpec> GetPayloadResponse<T> {
}
}
#[derive(Clone, Debug)]
pub struct ExecutionPayloadBodyV1<E: EthSpec> {
pub transactions: Transactions<E>,
pub withdrawals: Option<Withdrawals<E>>,
}
impl<E: EthSpec> ExecutionPayloadBodyV1<E> {
pub fn to_payload(
self,
header: ExecutionPayloadHeader<E>,
) -> Result<ExecutionPayload<E>, String> {
match header {
ExecutionPayloadHeader::Merge(header) => {
if self.withdrawals.is_some() {
return Err(format!(
"block {} is merge but payload body has withdrawals",
header.block_hash
));
}
Ok(ExecutionPayload::Merge(ExecutionPayloadMerge {
parent_hash: header.parent_hash,
fee_recipient: header.fee_recipient,
state_root: header.state_root,
receipts_root: header.receipts_root,
logs_bloom: header.logs_bloom,
prev_randao: header.prev_randao,
block_number: header.block_number,
gas_limit: header.gas_limit,
gas_used: header.gas_used,
timestamp: header.timestamp,
extra_data: header.extra_data,
base_fee_per_gas: header.base_fee_per_gas,
block_hash: header.block_hash,
transactions: self.transactions,
}))
}
ExecutionPayloadHeader::Capella(header) => {
if let Some(withdrawals) = self.withdrawals {
Ok(ExecutionPayload::Capella(ExecutionPayloadCapella {
parent_hash: header.parent_hash,
fee_recipient: header.fee_recipient,
state_root: header.state_root,
receipts_root: header.receipts_root,
logs_bloom: header.logs_bloom,
prev_randao: header.prev_randao,
block_number: header.block_number,
gas_limit: header.gas_limit,
gas_used: header.gas_used,
timestamp: header.timestamp,
extra_data: header.extra_data,
base_fee_per_gas: header.base_fee_per_gas,
block_hash: header.block_hash,
transactions: self.transactions,
withdrawals,
}))
} else {
Err(format!(
"block {} is capella but payload body doesn't have withdrawals",
header.block_hash
))
}
}
ExecutionPayloadHeader::Eip4844(header) => {
if let Some(withdrawals) = self.withdrawals {
Ok(ExecutionPayload::Eip4844(ExecutionPayloadEip4844 {
parent_hash: header.parent_hash,
fee_recipient: header.fee_recipient,
state_root: header.state_root,
receipts_root: header.receipts_root,
logs_bloom: header.logs_bloom,
prev_randao: header.prev_randao,
block_number: header.block_number,
gas_limit: header.gas_limit,
gas_used: header.gas_used,
timestamp: header.timestamp,
extra_data: header.extra_data,
base_fee_per_gas: header.base_fee_per_gas,
excess_data_gas: header.excess_data_gas,
block_hash: header.block_hash,
transactions: self.transactions,
withdrawals,
}))
} else {
Err(format!(
"block {} is post capella but payload body doesn't have withdrawals",
header.block_hash
))
}
}
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct EngineCapabilities {
pub new_payload_v1: bool,
@ -427,6 +522,8 @@ pub struct EngineCapabilities {
pub new_payload_v3: bool,
pub forkchoice_updated_v1: bool,
pub forkchoice_updated_v2: bool,
pub get_payload_bodies_by_hash_v1: bool,
pub get_payload_bodies_by_range_v1: bool,
pub get_payload_v1: bool,
pub get_payload_v2: bool,
pub get_payload_v3: bool,
@ -451,6 +548,12 @@ impl EngineCapabilities {
if self.forkchoice_updated_v2 {
response.push(ENGINE_FORKCHOICE_UPDATED_V2);
}
if self.get_payload_bodies_by_hash_v1 {
response.push(ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1);
}
if self.get_payload_bodies_by_range_v1 {
response.push(ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1);
}
if self.get_payload_v1 {
response.push(ENGINE_GET_PAYLOAD_V1);
}

View File

@ -47,6 +47,10 @@ pub const ENGINE_FORKCHOICE_UPDATED_V1: &str = "engine_forkchoiceUpdatedV1";
pub const ENGINE_FORKCHOICE_UPDATED_V2: &str = "engine_forkchoiceUpdatedV2";
pub const ENGINE_FORKCHOICE_UPDATED_TIMEOUT: Duration = Duration::from_secs(8);
pub const ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1: &str = "engine_getPayloadBodiesByHashV1";
pub const ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1: &str = "engine_getPayloadBodiesByRangeV1";
pub const ENGINE_GET_PAYLOAD_BODIES_TIMEOUT: Duration = Duration::from_secs(10);
pub const ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1: &str =
"engine_exchangeTransitionConfigurationV1";
pub const ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1_TIMEOUT: Duration = Duration::from_secs(1);
@ -69,6 +73,8 @@ pub static LIGHTHOUSE_CAPABILITIES: &[&str] = &[
ENGINE_GET_PAYLOAD_V3,
ENGINE_FORKCHOICE_UPDATED_V1,
ENGINE_FORKCHOICE_UPDATED_V2,
ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1,
ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1,
ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1,
];
@ -82,6 +88,8 @@ pub static PRE_CAPELLA_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilit
new_payload_v3: false,
forkchoice_updated_v1: true,
forkchoice_updated_v2: false,
get_payload_bodies_by_hash_v1: false,
get_payload_bodies_by_range_v1: false,
get_payload_v1: true,
get_payload_v2: false,
get_payload_v3: false,
@ -978,6 +986,50 @@ impl HttpJsonRpc {
Ok(response.into())
}
pub async fn get_payload_bodies_by_hash_v1<E: EthSpec>(
&self,
block_hashes: Vec<ExecutionBlockHash>,
) -> Result<Vec<Option<ExecutionPayloadBodyV1<E>>>, Error> {
let params = json!([block_hashes]);
let response: Vec<Option<JsonExecutionPayloadBodyV1<E>>> = self
.rpc_request(
ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1,
params,
ENGINE_GET_PAYLOAD_BODIES_TIMEOUT * self.execution_timeout_multiplier,
)
.await?;
Ok(response
.into_iter()
.map(|opt_json| opt_json.map(From::from))
.collect())
}
pub async fn get_payload_bodies_by_range_v1<E: EthSpec>(
&self,
start: u64,
count: u64,
) -> Result<Vec<Option<ExecutionPayloadBodyV1<E>>>, Error> {
#[derive(Serialize)]
#[serde(transparent)]
struct Quantity(#[serde(with = "eth2_serde_utils::u64_hex_be")] u64);
let params = json!([Quantity(start), Quantity(count)]);
let response: Vec<Option<JsonExecutionPayloadBodyV1<E>>> = self
.rpc_request(
ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1,
params,
ENGINE_GET_PAYLOAD_BODIES_TIMEOUT * self.execution_timeout_multiplier,
)
.await?;
Ok(response
.into_iter()
.map(|opt_json| opt_json.map(From::from))
.collect())
}
pub async fn exchange_transition_configuration_v1(
&self,
transition_configuration: TransitionConfigurationV1,
@ -1021,6 +1073,10 @@ impl HttpJsonRpc {
new_payload_v3: capabilities.contains(ENGINE_NEW_PAYLOAD_V3),
forkchoice_updated_v1: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V1),
forkchoice_updated_v2: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V2),
get_payload_bodies_by_hash_v1: capabilities
.contains(ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1),
get_payload_bodies_by_range_v1: capabilities
.contains(ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1),
get_payload_v1: capabilities.contains(ENGINE_GET_PAYLOAD_V1),
get_payload_v2: capabilities.contains(ENGINE_GET_PAYLOAD_V2),
get_payload_v3: capabilities.contains(ENGINE_GET_PAYLOAD_V3),

View File

@ -5,7 +5,7 @@ use superstruct::superstruct;
use types::blobs_sidecar::KzgCommitments;
use types::{
Blobs, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadCapella,
ExecutionPayloadEip4844, ExecutionPayloadMerge, FixedVector, Transaction, Unsigned,
ExecutionPayloadEip4844, ExecutionPayloadMerge, FixedVector, Transactions, Unsigned,
VariableList, Withdrawal,
};
@ -98,8 +98,7 @@ pub struct JsonExecutionPayload<T: EthSpec> {
pub excess_data_gas: Uint256,
pub block_hash: ExecutionBlockHash,
#[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")]
pub transactions:
VariableList<Transaction<T::MaxBytesPerTransaction>, T::MaxTransactionsPerPayload>,
pub transactions: Transactions<T>,
#[superstruct(only(V2, V3))]
pub withdrawals: VariableList<JsonWithdrawal, T::MaxWithdrawalsPerPayload>,
}
@ -572,6 +571,30 @@ impl From<ForkchoiceUpdatedResponse> for JsonForkchoiceUpdatedV1Response {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(bound = "E: EthSpec")]
pub struct JsonExecutionPayloadBodyV1<E: EthSpec> {
#[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")]
pub transactions: Transactions<E>,
pub withdrawals: Option<VariableList<JsonWithdrawal, E::MaxWithdrawalsPerPayload>>,
}
impl<E: EthSpec> From<JsonExecutionPayloadBodyV1<E>> for ExecutionPayloadBodyV1<E> {
fn from(value: JsonExecutionPayloadBodyV1<E>) -> Self {
Self {
transactions: value.transactions,
withdrawals: value.withdrawals.map(|json_withdrawals| {
Withdrawals::<E>::from(
json_withdrawals
.into_iter()
.map(Into::into)
.collect::<Vec<_>>(),
)
}),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransitionConfigurationV1 {

View File

@ -1644,6 +1644,37 @@ impl<T: EthSpec> ExecutionLayer<T> {
}
}
pub async fn get_payload_bodies_by_hash(
&self,
hashes: Vec<ExecutionBlockHash>,
) -> Result<Vec<Option<ExecutionPayloadBodyV1<T>>>, Error> {
self.engine()
.request(|engine: &Engine| async move {
engine.api.get_payload_bodies_by_hash_v1(hashes).await
})
.await
.map_err(Box::new)
.map_err(Error::EngineError)
}
pub async fn get_payload_bodies_by_range(
&self,
start: u64,
count: u64,
) -> Result<Vec<Option<ExecutionPayloadBodyV1<T>>>, Error> {
let _timer = metrics::start_timer(&metrics::EXECUTION_LAYER_GET_PAYLOAD_BODIES_BY_RANGE);
self.engine()
.request(|engine: &Engine| async move {
engine
.api
.get_payload_bodies_by_range_v1(start, count)
.await
})
.await
.map_err(Box::new)
.map_err(Error::EngineError)
}
pub async fn get_payload_by_block_hash(
&self,
hash: ExecutionBlockHash,

View File

@ -45,6 +45,10 @@ lazy_static::lazy_static! {
"execution_layer_get_payload_by_block_hash_time",
"Time to reconstruct a payload from the EE using eth_getBlockByHash"
);
pub static ref EXECUTION_LAYER_GET_PAYLOAD_BODIES_BY_RANGE: Result<Histogram> = try_create_histogram(
"execution_layer_get_payload_bodies_by_range_time",
"Time to fetch a range of payload bodies from the EE"
);
pub static ref EXECUTION_LAYER_VERIFY_BLOCK_HASH: Result<Histogram> = try_create_histogram_with_buckets(
"execution_layer_verify_block_hash_time",
"Time to verify the execution block hash in Lighthouse, without the EL",

View File

@ -205,6 +205,14 @@ impl<T: EthSpec> ExecutionBlockGenerator<T> {
.and_then(|block| block.as_execution_block_with_tx())
}
pub fn execution_block_with_txs_by_number(
&self,
number: u64,
) -> Option<ExecutionBlockWithTransactions<T>> {
self.block_by_number(number)
.and_then(|block| block.as_execution_block_with_tx())
}
pub fn move_to_block_prior_to_terminal_block(&mut self) -> Result<(), String> {
let target_block = self
.terminal_block_number

View File

@ -2,7 +2,7 @@ use super::Context;
use crate::engine_api::{http::*, *};
use crate::json_structures::*;
use crate::test_utils::DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI;
use serde::de::DeserializeOwned;
use serde::{de::DeserializeOwned, Deserialize};
use serde_json::Value as JsonValue;
use std::sync::Arc;
use types::{EthSpec, ForkName};
@ -429,6 +429,61 @@ pub async fn handle_rpc<T: EthSpec>(
let engine_capabilities = ctx.engine_capabilities.read();
Ok(serde_json::to_value(engine_capabilities.to_response()).unwrap())
}
ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1 => {
#[derive(Deserialize)]
#[serde(transparent)]
struct Quantity(#[serde(with = "eth2_serde_utils::u64_hex_be")] pub u64);
let start = get_param::<Quantity>(params, 0)
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?
.0;
let count = get_param::<Quantity>(params, 1)
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?
.0;
let mut response = vec![];
for block_num in start..(start + count) {
let maybe_block = ctx
.execution_block_generator
.read()
.execution_block_with_txs_by_number(block_num);
match maybe_block {
Some(block) => {
let transactions = Transactions::<T>::new(
block
.transactions()
.iter()
.map(|transaction| VariableList::new(transaction.rlp().to_vec()))
.collect::<Result<_, _>>()
.map_err(|e| {
(
format!("failed to deserialize transaction: {:?}", e),
GENERIC_ERROR_CODE,
)
})?,
)
.map_err(|e| {
(
format!("failed to deserialize transactions: {:?}", e),
GENERIC_ERROR_CODE,
)
})?;
response.push(Some(JsonExecutionPayloadBodyV1::<T> {
transactions,
withdrawals: block
.withdrawals()
.ok()
.map(|withdrawals| VariableList::from(withdrawals.clone())),
}));
}
None => response.push(None),
}
}
Ok(serde_json::to_value(response).unwrap())
}
other => Err((
format!("The method {} does not exist/is not available", other),
METHOD_NOT_FOUND_CODE,

View File

@ -40,6 +40,8 @@ pub const DEFAULT_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities {
new_payload_v3: true,
forkchoice_updated_v1: true,
forkchoice_updated_v2: true,
get_payload_bodies_by_hash_v1: true,
get_payload_bodies_by_range_v1: true,
get_payload_v1: true,
get_payload_v2: true,
get_payload_v3: true,

View File

@ -56,8 +56,8 @@ use system_health::observe_system_health_bn;
use tokio::sync::mpsc::{Sender, UnboundedSender};
use tokio_stream::{wrappers::BroadcastStream, StreamExt};
use types::{
Attestation, AttestationData, AttesterSlashing, BeaconStateError, BlindedPayload,
CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName, FullPayload,
Attestation, AttestationData, AttestationShufflingId, AttesterSlashing, BeaconStateError,
BlindedPayload, CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName, FullPayload,
ProposerPreparationData, ProposerSlashing, RelativeEpoch, SignedAggregateAndProof,
SignedBeaconBlock, SignedBlindedBeaconBlock, SignedBlsToExecutionChange,
SignedContributionAndProof, SignedValidatorRegistrationData, SignedVoluntaryExit, Slot,
@ -786,39 +786,112 @@ pub fn serve<T: BeaconChainTypes>(
let current_epoch = state.current_epoch();
let epoch = query.epoch.unwrap_or(current_epoch);
let committee_cache =
match RelativeEpoch::from_epoch(current_epoch, epoch) {
Ok(relative_epoch)
if state
.committee_cache_is_initialized(relative_epoch) =>
{
state.committee_cache(relative_epoch).map(Cow::Borrowed)
}
_ => CommitteeCache::initialized(state, epoch, &chain.spec)
// Attempt to obtain the committee_cache from the beacon chain
let decision_slot = (epoch.saturating_sub(2u64))
.end_slot(T::EthSpec::slots_per_epoch());
// Find the decision block and skip to another method on any kind
// of failure
let shuffling_id = if let Ok(Some(shuffling_decision_block)) =
chain.block_root_at_slot(decision_slot, WhenSlotSkipped::Prev)
{
Some(AttestationShufflingId {
shuffling_epoch: epoch,
shuffling_decision_block,
})
} else {
None
};
// Attempt to read from the chain cache if there exists a
// shuffling_id
let maybe_cached_shuffling = if let Some(shuffling_id) =
shuffling_id.as_ref()
{
chain
.shuffling_cache
.try_write_for(std::time::Duration::from_secs(1))
.and_then(|mut cache_write| cache_write.get(shuffling_id))
.and_then(|cache_item| cache_item.wait().ok())
} else {
None
};
let committee_cache = if let Some(ref shuffling) =
maybe_cached_shuffling
{
Cow::Borrowed(&**shuffling)
} else {
let possibly_built_cache =
match RelativeEpoch::from_epoch(current_epoch, epoch) {
Ok(relative_epoch)
if state.committee_cache_is_initialized(
relative_epoch,
) =>
{
state
.committee_cache(relative_epoch)
.map(Cow::Borrowed)
}
_ => CommitteeCache::initialized(
state,
epoch,
&chain.spec,
)
.map(Cow::Owned),
}
.map_err(|e| match e {
BeaconStateError::EpochOutOfBounds => {
let max_sprp =
T::EthSpec::slots_per_historical_root() as u64;
let first_subsequent_restore_point_slot = ((epoch
.start_slot(T::EthSpec::slots_per_epoch())
/ max_sprp)
+ 1)
* max_sprp;
if epoch < current_epoch {
warp_utils::reject::custom_bad_request(format!(
"epoch out of bounds, try state at slot {}",
first_subsequent_restore_point_slot,
))
} else {
warp_utils::reject::custom_bad_request(
"epoch out of bounds, too far in future".into(),
)
}
.map_err(|e| {
match e {
BeaconStateError::EpochOutOfBounds => {
let max_sprp =
T::EthSpec::slots_per_historical_root()
as u64;
let first_subsequent_restore_point_slot =
((epoch.start_slot(
T::EthSpec::slots_per_epoch(),
) / max_sprp)
+ 1)
* max_sprp;
if epoch < current_epoch {
warp_utils::reject::custom_bad_request(
format!(
"epoch out of bounds, \
try state at slot {}",
first_subsequent_restore_point_slot,
),
)
} else {
warp_utils::reject::custom_bad_request(
"epoch out of bounds, \
too far in future"
.into(),
)
}
}
_ => {
warp_utils::reject::beacon_chain_error(e.into())
}
}
})?;
// Attempt to write to the beacon cache (only if the cache
// size is not the default value).
if chain.config.shuffling_cache_size
!= beacon_chain::shuffling_cache::DEFAULT_CACHE_SIZE
{
if let Some(shuffling_id) = shuffling_id {
if let Some(mut cache_write) = chain
.shuffling_cache
.try_write_for(std::time::Duration::from_secs(1))
{
cache_write.insert_committee_cache(
shuffling_id,
&*possibly_built_cache,
);
}
}
_ => warp_utils::reject::beacon_chain_error(e.into()),
})?;
}
possibly_built_cache
};
// Use either the supplied slot or all slots in the epoch.
let slots =

View File

@ -5,7 +5,7 @@ authors = ["Sigma Prime <contact@sigmaprime.io>"]
edition = "2021"
[dependencies]
discv5 = { version = "0.1.0", features = ["libp2p"] }
discv5 = { version = "0.2.2", features = ["libp2p"] }
unsigned-varint = { version = "0.6.0", features = ["codec"] }
types = { path = "../../consensus/types" }
eth2_ssz_types = "0.2.2"

View File

@ -177,6 +177,13 @@ pub struct Discovery<TSpec: EthSpec> {
/// always false.
pub started: bool,
/// This keeps track of whether an external UDP port change should also indicate an internal
/// TCP port change. As we cannot detect our external TCP port, we assume that the external UDP
/// port is also our external TCP port. This assumption only holds if the user has not
/// explicitly set their ENR TCP port via the CLI config. The first indicates tcp4 and the
/// second indicates tcp6.
update_tcp_port: (bool, bool),
/// Logger for the discovery behaviour.
log: slog::Logger,
}
@ -197,6 +204,7 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
};
let local_enr = network_globals.local_enr.read().clone();
let local_node_id = local_enr.node_id();
info!(log, "ENR Initialised"; "enr" => local_enr.to_base64(), "seq" => local_enr.seq(), "id"=> %local_enr.node_id(),
"ip4" => ?local_enr.ip4(), "udp4"=> ?local_enr.udp4(), "tcp4" => ?local_enr.tcp6()
@ -217,6 +225,10 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
// Add bootnodes to routing table
for bootnode_enr in config.boot_nodes_enr.clone() {
if bootnode_enr.node_id() == local_node_id {
// If we are a boot node, ignore adding it to the routing table
continue;
}
debug!(
log,
"Adding node to routing table";
@ -295,6 +307,11 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
}
}
let update_tcp_port = (
config.enr_tcp4_port.is_none(),
config.enr_tcp6_port.is_none(),
);
Ok(Self {
cached_enrs: LruCache::new(50),
network_globals,
@ -304,6 +321,7 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
discv5,
event_stream,
started: !config.disable_discovery,
update_tcp_port,
log,
enr_dir,
})
@ -1014,6 +1032,13 @@ impl<TSpec: EthSpec> NetworkBehaviour for Discovery<TSpec> {
metrics::check_nat();
// Discv5 will have updated our local ENR. We save the updated version
// to disk.
if (self.update_tcp_port.0 && socket_addr.is_ipv4())
|| (self.update_tcp_port.1 && socket_addr.is_ipv6())
{
// Update the TCP port in the ENR
self.discv5.update_local_enr_socket(socket_addr, true);
}
let enr = self.discv5.local_enr();
enr::save_enr_to_disk(Path::new(&self.enr_dir), &enr, &self.log);
// update network globals

View File

@ -167,7 +167,8 @@ pub fn check_nat() {
}
pub fn scrape_discovery_metrics() {
let metrics = discv5::metrics::Metrics::from(discv5::Discv5::raw_metrics());
let metrics =
discv5::metrics::Metrics::from(discv5::Discv5::<discv5::DefaultProtocolId>::raw_metrics());
set_float_gauge(&DISCOVERY_REQS, metrics.unsolicited_requests_per_second);
set_gauge(&DISCOVERY_SESSIONS, metrics.active_sessions as i64);
set_gauge(&DISCOVERY_SENT_BYTES, metrics.bytes_sent as i64);

View File

@ -572,6 +572,9 @@ impl<T: BeaconChainTypes> ReprocessQueue<T> {
}) => {
// Unqueue the attestations we have for this root, if any.
if let Some(queued_ids) = self.awaiting_attestations_per_root.remove(&block_root) {
let mut sent_count = 0;
let mut failed_to_send_count = 0;
for id in queued_ids {
metrics::inc_counter(
&metrics::BEACON_PROCESSOR_REPROCESSING_QUEUE_MATCHED_ATTESTATIONS,
@ -596,10 +599,9 @@ impl<T: BeaconChainTypes> ReprocessQueue<T> {
// Send the work.
if self.ready_work_tx.try_send(work).is_err() {
error!(
log,
"Failed to send scheduled attestation";
);
failed_to_send_count += 1;
} else {
sent_count += 1;
}
} else {
// There is a mismatch between the attestation ids registered for this
@ -612,6 +614,18 @@ impl<T: BeaconChainTypes> ReprocessQueue<T> {
);
}
}
if failed_to_send_count > 0 {
error!(
log,
"Ignored scheduled attestation(s) for block";
"hint" => "system may be overloaded",
"parent_root" => ?parent_root,
"block_root" => ?block_root,
"failed_count" => failed_to_send_count,
"sent_count" => sent_count,
);
}
}
// Unqueue the light client optimistic updates we have for this root, if any.
if let Some(queued_lc_id) = self
@ -726,7 +740,9 @@ impl<T: BeaconChainTypes> ReprocessQueue<T> {
if self.ready_work_tx.try_send(work).is_err() {
error!(
log,
"Failed to send scheduled attestation";
"Ignored scheduled attestation";
"hint" => "system may be overloaded",
"beacon_block_root" => ?root
);
}

View File

@ -1,3 +1,5 @@
use std::sync::Arc;
use crate::beacon_processor::{worker::FUTURE_SLOT_TOLERANCE, SendOnDrop};
use crate::service::NetworkMessage;
use crate::status::ToStatusMessage;
@ -15,9 +17,9 @@ use slot_clock::SlotClock;
use std::collections::{hash_map::Entry, HashMap};
use std::sync::Arc;
use task_executor::TaskExecutor;
use tokio_stream::StreamExt;
use types::blob_sidecar::BlobIdentifier;
use types::light_client_bootstrap::LightClientBootstrap;
use types::{Epoch, EthSpec, Hash256, Slot};
use types::{light_client_bootstrap::LightClientBootstrap, Epoch, EthSpec, Hash256, Slot};
use super::Worker;
@ -140,21 +142,25 @@ impl<T: BeaconChainTypes> Worker<T> {
request_id: PeerRequestId,
request: BlocksByRootRequest,
) {
let requested_blocks = request.block_roots.len();
let mut block_stream = match self
.chain
.get_blocks_checking_early_attester_cache(request.block_roots.into(), &executor)
{
Ok(block_stream) => block_stream,
Err(e) => return error!(self.log, "Error getting block stream"; "error" => ?e),
};
// Fetching blocks is async because it may have to hit the execution layer for payloads.
executor.spawn(
async move {
let mut send_block_count = 0;
let mut send_response = true;
for root in request.block_roots.iter() {
match self
.chain
.get_block_checking_early_attester_cache(root)
.await
{
while let Some((root, result)) = block_stream.next().await {
match result.as_ref() {
Ok(Some(block)) => {
self.send_response(
peer_id,
Response::BlocksByRoot(Some(block)),
Response::BlocksByRoot(Some(block.clone())),
request_id,
);
send_block_count += 1;
@ -199,8 +205,8 @@ impl<T: BeaconChainTypes> Worker<T> {
self.log,
"Received BlocksByRoot Request";
"peer" => %peer_id,
"requested" => request.block_roots.len(),
"returned" => send_block_count
"requested" => requested_blocks,
"returned" => %send_block_count
);
// send stream termination
@ -537,14 +543,19 @@ impl<T: BeaconChainTypes> Worker<T> {
// remove all skip slots
let block_roots = block_roots.into_iter().flatten().collect::<Vec<_>>();
let mut block_stream = match self.chain.get_blocks(block_roots, &executor) {
Ok(block_stream) => block_stream,
Err(e) => return error!(self.log, "Error getting block stream"; "error" => ?e),
};
// Fetching blocks is async because it may have to hit the execution layer for payloads.
executor.spawn(
async move {
let mut blocks_sent = 0;
let mut send_response = true;
for root in block_roots {
match self.chain.get_block(&root).await {
while let Some((root, result)) = block_stream.next().await {
match result.as_ref() {
Ok(Some(block)) => {
// Due to skip slots, blocks could be out of the range, we ensure they
// are in the range before sending
@ -554,7 +565,7 @@ impl<T: BeaconChainTypes> Worker<T> {
blocks_sent += 1;
self.send_network_message(NetworkMessage::SendResponse {
peer_id,
response: Response::BlocksByRange(Some(Arc::new(block))),
response: Response::BlocksByRange(Some(block.clone())),
id: request_id,
});
}

View File

@ -377,6 +377,14 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
address of this server (e.g., http://localhost:5054).")
.takes_value(true),
)
.arg(
Arg::with_name("shuffling-cache-size")
.long("shuffling-cache-size")
.help("Some HTTP API requests can be optimised by caching the shufflings at each epoch. \
This flag allows the user to set the shuffling cache size in epochs. \
Shufflings are dependent on validator count and setting this value to a large number can consume a large amount of memory.")
.takes_value(true)
)
/*
* Monitoring metrics
@ -1000,8 +1008,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
Arg::with_name("count-unrealized")
.long("count-unrealized")
.hidden(true)
.help("Enables an alternative, potentially more performant FFG \
vote tracking method.")
.help("This flag is deprecated and has no effect.")
.takes_value(true)
.default_value("true")
)
@ -1009,7 +1016,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
Arg::with_name("count-unrealized-full")
.long("count-unrealized-full")
.hidden(true)
.help("Stricter version of `count-unrealized`.")
.help("This flag is deprecated and has no effect.")
.takes_value(true)
.default_value("false")
)

View File

@ -149,6 +149,10 @@ pub fn get_config<E: EthSpec>(
client_config.http_api.allow_sync_stalled = true;
}
if let Some(cache_size) = clap_utils::parse_optional(cli_args, "shuffling-cache-size")? {
client_config.chain.shuffling_cache_size = cache_size;
}
/*
* Prometheus metrics HTTP server
*/
@ -742,10 +746,21 @@ pub fn get_config<E: EthSpec>(
client_config.chain.fork_choice_before_proposal_timeout_ms = timeout;
}
client_config.chain.count_unrealized =
clap_utils::parse_required(cli_args, "count-unrealized")?;
client_config.chain.count_unrealized_full =
clap_utils::parse_required::<bool>(cli_args, "count-unrealized-full")?.into();
if !clap_utils::parse_required::<bool>(cli_args, "count-unrealized")? {
warn!(
log,
"The flag --count-unrealized is deprecated and will be removed";
"info" => "any use of the flag will have no effect"
);
}
if clap_utils::parse_required::<bool>(cli_args, "count-unrealized-full")? {
warn!(
log,
"The flag --count-unrealized-full is deprecated and will be removed";
"info" => "setting it to `true` has no effect"
);
}
client_config.chain.always_reset_payload_statuses =
cli_args.is_present("reset-payload-statuses");

View File

@ -4,7 +4,7 @@ use ssz::{Decode, Encode};
use ssz_derive::{Decode, Encode};
use types::{Checkpoint, Hash256, Slot};
pub const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(15);
pub const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(16);
// All the keys that get stored under the `BeaconMeta` column.
//

View File

@ -41,7 +41,7 @@ drastically and use the (recommended) default.
### NAT Traversal (Port Forwarding)
Lighthouse, by default, used port 9000 for both TCP and UDP. Lighthouse will
Lighthouse, by default, uses port 9000 for both TCP and UDP. Lighthouse will
still function if it is behind a NAT without any port mappings. Although
Lighthouse still functions, we recommend that some mechanism is used to ensure
that your Lighthouse node is publicly accessible. This will typically improve
@ -54,6 +54,16 @@ node will inform you of established routes in this case). If UPnP is not
enabled, we recommend you manually set up port mappings to both of Lighthouse's
TCP and UDP ports (9000 by default).
> Note: Lighthouse needs to advertise its publicly accessible ports in
> order to inform its peers that it is contactable and how to connect to it.
> Lighthouse has an automated way of doing this for the UDP port. This means
> Lighthouse can detect its external UDP port. There is no such mechanism for the
> TCP port. As such, we assume that the external UDP and external TCP port is the
> same (i.e external 5050 UDP/TCP mapping to internal 9000 is fine). If you are setting up differing external UDP and TCP ports, you should
> explicitly specify them using the `--enr-tcp-port` and `--enr-udp-port` as
> explained in the following section.
### ENR Configuration
Lighthouse has a number of CLI parameters for constructing and modifying the

View File

@ -128,8 +128,9 @@ same `datadir` as a previous network. I.e if you have been running the
`datadir` (the `datadir` is also printed out in the beacon node's logs on
boot-up).
If you find yourself with a low peer count and is not reaching the target you
expect. Try setting up the correct port forwards as described [here](./advanced_networking.md#nat-traversal-port-forwarding).
If you find yourself with a low peer count and it's not reaching the target you
expect. Try setting up the correct port forwards as described
[here](./advanced_networking.md#nat-traversal-port-forwarding).
### What should I do if I lose my slashing protection database?

View File

@ -1,6 +1,6 @@
[package]
name = "boot_node"
version = "3.5.1"
version = "4.0.1-rc.0"
authors = ["Sigma Prime <contact@sigmaprime.io>"]
edition = "2021"

View File

@ -44,7 +44,7 @@ pub async fn run<T: EthSpec>(config: BootNodeConfig<T>, log: slog::Logger) {
info!(log, "Contact information"; "multiaddrs" => ?local_enr.multiaddr_p2p());
// construct the discv5 server
let mut discv5 = Discv5::new(local_enr.clone(), local_key, discv5_config).unwrap();
let mut discv5: Discv5 = Discv5::new(local_enr.clone(), local_key, discv5_config).unwrap();
// If there are any bootnodes add them to the routing table
for enr in boot_nodes {

View File

@ -20,4 +20,4 @@ types = { path = "../../consensus/types"}
kzg = { path = "../../crypto/kzg" }
eth2_ssz = "0.4.1"
eth2_config = { path = "../eth2_config"}
enr = { version = "0.6.2", features = ["ed25519", "k256"] }
discv5 = "0.2.2"

View File

@ -38,7 +38,7 @@ BELLATRIX_FORK_VERSION: 0x02000000
BELLATRIX_FORK_EPOCH: 144896 # Sept 6, 2022, 11:34:47am UTC
# Capella
CAPELLA_FORK_VERSION: 0x03000000
CAPELLA_FORK_EPOCH: 18446744073709551615
CAPELLA_FORK_EPOCH: 194048 # April 12, 2023, 10:27:35pm UTC
# Eip4844
EIP4844_FORK_VERSION: 0x04000000
EIP4844_FORK_EPOCH: 18446744073709551615

View File

@ -11,7 +11,7 @@
//! To add a new built-in testnet, add it to the `define_hardcoded_nets` invocation in the `eth2_config`
//! crate.
use enr::{CombinedKey, Enr};
use discv5::enr::{CombinedKey, Enr};
use eth2_config::{instantiate_hardcoded_nets, HardcodedNet};
use kzg::TrustedSetup;
use std::fs::{create_dir_all, File};

View File

@ -17,8 +17,8 @@ pub const VERSION: &str = git_version!(
// NOTE: using --match instead of --exclude for compatibility with old Git
"--match=thiswillnevermatchlol"
],
prefix = "Lighthouse/v3.5.1-",
fallback = "Lighthouse/v3.5.1"
prefix = "Lighthouse/v4.0.1-rc.0-",
fallback = "Lighthouse/v4.0.1-rc.0"
);
/// Returns `VERSION`, but with platform information appended to the end.

View File

@ -1,6 +1,6 @@
use crate::{ForkChoiceStore, InvalidationOperation};
use proto_array::{
Block as ProtoBlock, CountUnrealizedFull, ExecutionStatus, ProposerHeadError, ProposerHeadInfo,
Block as ProtoBlock, ExecutionStatus, ProposerHeadError, ProposerHeadInfo,
ProtoArrayForkChoice, ReOrgThreshold,
};
use slog::{crit, debug, warn, Logger};
@ -187,51 +187,6 @@ impl CountUnrealized {
pub fn is_true(&self) -> bool {
matches!(self, CountUnrealized::True)
}
pub fn and(&self, other: CountUnrealized) -> CountUnrealized {
if self.is_true() && other.is_true() {
CountUnrealized::True
} else {
CountUnrealized::False
}
}
}
impl From<bool> for CountUnrealized {
fn from(count_unrealized: bool) -> Self {
if count_unrealized {
CountUnrealized::True
} else {
CountUnrealized::False
}
}
}
#[derive(Copy, Clone)]
enum UpdateJustifiedCheckpointSlots {
OnTick {
current_slot: Slot,
},
OnBlock {
state_slot: Slot,
current_slot: Slot,
},
}
impl UpdateJustifiedCheckpointSlots {
fn current_slot(&self) -> Slot {
match self {
UpdateJustifiedCheckpointSlots::OnTick { current_slot } => *current_slot,
UpdateJustifiedCheckpointSlots::OnBlock { current_slot, .. } => *current_slot,
}
}
fn state_slot(&self) -> Option<Slot> {
match self {
UpdateJustifiedCheckpointSlots::OnTick { .. } => None,
UpdateJustifiedCheckpointSlots::OnBlock { state_slot, .. } => Some(*state_slot),
}
}
}
/// Indicates if a block has been verified by an execution payload.
@ -393,7 +348,6 @@ where
anchor_block: &SignedBeaconBlock<E>,
anchor_state: &BeaconState<E>,
current_slot: Option<Slot>,
count_unrealized_full_config: CountUnrealizedFull,
spec: &ChainSpec,
) -> Result<Self, Error<T::Error>> {
// Sanity check: the anchor must lie on an epoch boundary.
@ -440,7 +394,6 @@ where
current_epoch_shuffling_id,
next_epoch_shuffling_id,
execution_status,
count_unrealized_full_config,
)?;
let mut fork_choice = Self {
@ -533,7 +486,7 @@ where
// Provide the slot (as per the system clock) to the `fc_store` and then return its view of
// the current slot. The `fc_store` will ensure that the `current_slot` is never
// decreasing, a property which we must maintain.
let current_slot = self.update_time(system_time_current_slot, spec)?;
let current_slot = self.update_time(system_time_current_slot)?;
let store = &mut self.fc_store;
@ -654,58 +607,6 @@ where
}
}
/// Returns `true` if the given `store` should be updated to set
/// `state.current_justified_checkpoint` its `justified_checkpoint`.
///
/// ## Specification
///
/// Is equivalent to:
///
/// https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/fork-choice.md#should_update_justified_checkpoint
fn should_update_justified_checkpoint(
&mut self,
new_justified_checkpoint: Checkpoint,
slots: UpdateJustifiedCheckpointSlots,
spec: &ChainSpec,
) -> Result<bool, Error<T::Error>> {
self.update_time(slots.current_slot(), spec)?;
if compute_slots_since_epoch_start::<E>(self.fc_store.get_current_slot())
< spec.safe_slots_to_update_justified
{
return Ok(true);
}
let justified_slot =
compute_start_slot_at_epoch::<E>(self.fc_store.justified_checkpoint().epoch);
// This sanity check is not in the spec, but the invariant is implied.
if let Some(state_slot) = slots.state_slot() {
if justified_slot >= state_slot {
return Err(Error::AttemptToRevertJustification {
store: justified_slot,
state: state_slot,
});
}
}
// We know that the slot for `new_justified_checkpoint.root` is not greater than
// `state.slot`, since a state cannot justify its own slot.
//
// We know that `new_justified_checkpoint.root` is an ancestor of `state`, since a `state`
// only ever justifies ancestors.
//
// A prior `if` statement protects against a justified_slot that is greater than
// `state.slot`
let justified_ancestor =
self.get_ancestor(new_justified_checkpoint.root, justified_slot)?;
if justified_ancestor != Some(self.fc_store.justified_checkpoint().root) {
return Ok(false);
}
Ok(true)
}
/// See `ProtoArrayForkChoice::process_execution_payload_validation` for documentation.
pub fn on_valid_execution_payload(
&mut self,
@ -759,7 +660,7 @@ where
// Provide the slot (as per the system clock) to the `fc_store` and then return its view of
// the current slot. The `fc_store` will ensure that the `current_slot` is never
// decreasing, a property which we must maintain.
let current_slot = self.update_time(system_time_current_slot, spec)?;
let current_slot = self.update_time(system_time_current_slot)?;
// Parent block must be known.
let parent_block = self
@ -814,17 +715,10 @@ where
self.fc_store.set_proposer_boost_root(block_root);
}
let update_justified_checkpoint_slots = UpdateJustifiedCheckpointSlots::OnBlock {
state_slot: state.slot(),
current_slot,
};
// Update store with checkpoints if necessary
self.update_checkpoints(
state.current_justified_checkpoint(),
state.finalized_checkpoint(),
update_justified_checkpoint_slots,
spec,
)?;
// Update unrealized justified/finalized checkpoints.
@ -908,11 +802,9 @@ where
// If block is from past epochs, try to update store's justified & finalized checkpoints right away
if block.slot().epoch(E::slots_per_epoch()) < current_slot.epoch(E::slots_per_epoch()) {
self.update_checkpoints(
self.pull_up_store_checkpoints(
unrealized_justified_checkpoint,
unrealized_finalized_checkpoint,
update_justified_checkpoint_slots,
spec,
)?;
}
@ -1007,29 +899,19 @@ where
&mut self,
justified_checkpoint: Checkpoint,
finalized_checkpoint: Checkpoint,
slots: UpdateJustifiedCheckpointSlots,
spec: &ChainSpec,
) -> Result<(), Error<T::Error>> {
// Update justified checkpoint.
if justified_checkpoint.epoch > self.fc_store.justified_checkpoint().epoch {
if justified_checkpoint.epoch > self.fc_store.best_justified_checkpoint().epoch {
self.fc_store
.set_best_justified_checkpoint(justified_checkpoint);
}
if self.should_update_justified_checkpoint(justified_checkpoint, slots, spec)? {
self.fc_store
.set_justified_checkpoint(justified_checkpoint)
.map_err(Error::UnableToSetJustifiedCheckpoint)?;
}
self.fc_store
.set_justified_checkpoint(justified_checkpoint)
.map_err(Error::UnableToSetJustifiedCheckpoint)?;
}
// Update finalized checkpoint.
if finalized_checkpoint.epoch > self.fc_store.finalized_checkpoint().epoch {
self.fc_store.set_finalized_checkpoint(finalized_checkpoint);
self.fc_store
.set_justified_checkpoint(justified_checkpoint)
.map_err(Error::UnableToSetJustifiedCheckpoint)?;
}
Ok(())
}
@ -1170,9 +1052,8 @@ where
system_time_current_slot: Slot,
attestation: &IndexedAttestation<E>,
is_from_block: AttestationFromBlock,
spec: &ChainSpec,
) -> Result<(), Error<T::Error>> {
self.update_time(system_time_current_slot, spec)?;
self.update_time(system_time_current_slot)?;
// Ignore any attestations to the zero hash.
//
@ -1233,16 +1114,12 @@ where
/// Call `on_tick` for all slots between `fc_store.get_current_slot()` and the provided
/// `current_slot`. Returns the value of `self.fc_store.get_current_slot`.
pub fn update_time(
&mut self,
current_slot: Slot,
spec: &ChainSpec,
) -> Result<Slot, Error<T::Error>> {
pub fn update_time(&mut self, current_slot: Slot) -> Result<Slot, Error<T::Error>> {
while self.fc_store.get_current_slot() < current_slot {
let previous_slot = self.fc_store.get_current_slot();
// Note: we are relying upon `on_tick` to update `fc_store.time` to ensure we don't
// get stuck in a loop.
self.on_tick(previous_slot + 1, spec)?
self.on_tick(previous_slot + 1)?
}
// Process any attestations that might now be eligible.
@ -1258,7 +1135,7 @@ where
/// Equivalent to:
///
/// https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/fork-choice.md#on_tick
fn on_tick(&mut self, time: Slot, spec: &ChainSpec) -> Result<(), Error<T::Error>> {
fn on_tick(&mut self, time: Slot) -> Result<(), Error<T::Error>> {
let store = &mut self.fc_store;
let previous_slot = store.get_current_slot();
@ -1286,26 +1163,27 @@ where
return Ok(());
}
if store.best_justified_checkpoint().epoch > store.justified_checkpoint().epoch {
let store = &self.fc_store;
if self.is_finalized_checkpoint_or_descendant(store.best_justified_checkpoint().root) {
let store = &mut self.fc_store;
store
.set_justified_checkpoint(*store.best_justified_checkpoint())
.map_err(Error::ForkChoiceStoreError)?;
}
}
// Update store.justified_checkpoint if a better unrealized justified checkpoint is known
// Update the justified/finalized checkpoints based upon the
// best-observed unrealized justification/finality.
let unrealized_justified_checkpoint = *self.fc_store.unrealized_justified_checkpoint();
let unrealized_finalized_checkpoint = *self.fc_store.unrealized_finalized_checkpoint();
self.pull_up_store_checkpoints(
unrealized_justified_checkpoint,
unrealized_finalized_checkpoint,
)?;
Ok(())
}
fn pull_up_store_checkpoints(
&mut self,
unrealized_justified_checkpoint: Checkpoint,
unrealized_finalized_checkpoint: Checkpoint,
) -> Result<(), Error<T::Error>> {
self.update_checkpoints(
unrealized_justified_checkpoint,
unrealized_finalized_checkpoint,
UpdateJustifiedCheckpointSlots::OnTick { current_slot },
spec,
)?;
Ok(())
)
}
/// Processes and removes from the queue any queued attestations which may now be eligible for
@ -1471,16 +1349,6 @@ where
*self.fc_store.justified_checkpoint()
}
/// Return the best justified checkpoint.
///
/// ## Warning
///
/// This is distinct to the "justified checkpoint" or the "current justified checkpoint". This
/// "best justified checkpoint" value should only be used internally or for testing.
pub fn best_justified_checkpoint(&self) -> Checkpoint {
*self.fc_store.best_justified_checkpoint()
}
pub fn unrealized_justified_checkpoint(&self) -> Checkpoint {
*self.fc_store.unrealized_justified_checkpoint()
}
@ -1541,13 +1409,11 @@ where
pub fn proto_array_from_persisted(
persisted: &PersistedForkChoice,
reset_payload_statuses: ResetPayloadStatuses,
count_unrealized_full: CountUnrealizedFull,
spec: &ChainSpec,
log: &Logger,
) -> Result<ProtoArrayForkChoice, Error<T::Error>> {
let mut proto_array =
ProtoArrayForkChoice::from_bytes(&persisted.proto_array_bytes, count_unrealized_full)
.map_err(Error::InvalidProtoArrayBytes)?;
let mut proto_array = ProtoArrayForkChoice::from_bytes(&persisted.proto_array_bytes)
.map_err(Error::InvalidProtoArrayBytes)?;
let contains_invalid_payloads = proto_array.contains_invalid_payloads();
debug!(
@ -1578,7 +1444,7 @@ where
"error" => e,
"info" => "please report this error",
);
ProtoArrayForkChoice::from_bytes(&persisted.proto_array_bytes, count_unrealized_full)
ProtoArrayForkChoice::from_bytes(&persisted.proto_array_bytes)
.map_err(Error::InvalidProtoArrayBytes)
} else {
debug!(
@ -1595,17 +1461,11 @@ where
persisted: PersistedForkChoice,
reset_payload_statuses: ResetPayloadStatuses,
fc_store: T,
count_unrealized_full: CountUnrealizedFull,
spec: &ChainSpec,
log: &Logger,
) -> Result<Self, Error<T::Error>> {
let proto_array = Self::proto_array_from_persisted(
&persisted,
reset_payload_statuses,
count_unrealized_full,
spec,
log,
)?;
let proto_array =
Self::proto_array_from_persisted(&persisted, reset_payload_statuses, spec, log)?;
let current_slot = fc_store.get_current_slot();

View File

@ -47,9 +47,6 @@ pub trait ForkChoiceStore<T: EthSpec>: Sized {
/// Returns balances from the `state` identified by `justified_checkpoint.root`.
fn justified_balances(&self) -> &JustifiedBalances;
/// Returns the `best_justified_checkpoint`.
fn best_justified_checkpoint(&self) -> &Checkpoint;
/// Returns the `finalized_checkpoint`.
fn finalized_checkpoint(&self) -> &Checkpoint;
@ -68,9 +65,6 @@ pub trait ForkChoiceStore<T: EthSpec>: Sized {
/// Sets the `justified_checkpoint`.
fn set_justified_checkpoint(&mut self, checkpoint: Checkpoint) -> Result<(), Self::Error>;
/// Sets the `best_justified_checkpoint`.
fn set_best_justified_checkpoint(&mut self, checkpoint: Checkpoint);
/// Sets the `unrealized_justified_checkpoint`.
fn set_unrealized_justified_checkpoint(&mut self, checkpoint: Checkpoint);

View File

@ -7,6 +7,4 @@ pub use crate::fork_choice::{
PersistedForkChoice, QueuedAttestation, ResetPayloadStatuses,
};
pub use fork_choice_store::ForkChoiceStore;
pub use proto_array::{
Block as ProtoBlock, CountUnrealizedFull, ExecutionStatus, InvalidationOperation,
};
pub use proto_array::{Block as ProtoBlock, ExecutionStatus, InvalidationOperation};

View File

@ -104,16 +104,6 @@ impl ForkChoiceTest {
self
}
/// Assert the epochs match.
pub fn assert_best_justified_epoch(self, epoch: u64) -> Self {
assert_eq!(
self.get(|fc_store| fc_store.best_justified_checkpoint().epoch),
Epoch::new(epoch),
"best_justified_epoch"
);
self
}
/// Assert the given slot is greater than the head slot.
pub fn assert_finalized_epoch_is_less_than(self, epoch: Epoch) -> Self {
assert!(self.harness.finalized_checkpoint().epoch < epoch);
@ -151,7 +141,7 @@ impl ForkChoiceTest {
.chain
.canonical_head
.fork_choice_write_lock()
.update_time(self.harness.chain.slot().unwrap(), &self.harness.spec)
.update_time(self.harness.chain.slot().unwrap())
.unwrap();
func(
self.harness
@ -241,6 +231,11 @@ impl ForkChoiceTest {
///
/// If the chain is presently in an unsafe period, transition through it and the following safe
/// period.
///
/// Note: the `SAFE_SLOTS_TO_UPDATE_JUSTIFIED` variable has been removed
/// from the fork choice spec in Q1 2023. We're still leaving references to
/// it in our tests because (a) it's easier and (b) it allows us to easily
/// test for the absence of that parameter.
pub fn move_to_next_unsafe_period(self) -> Self {
self.move_inside_safe_to_update()
.move_outside_safe_to_update()
@ -534,7 +529,6 @@ async fn justified_checkpoint_updates_with_descendent_outside_safe_slots() {
.unwrap()
.move_outside_safe_to_update()
.assert_justified_epoch(2)
.assert_best_justified_epoch(2)
.apply_blocks(1)
.await
.assert_justified_epoch(3);
@ -551,11 +545,9 @@ async fn justified_checkpoint_updates_first_justification_outside_safe_to_update
.unwrap()
.move_to_next_unsafe_period()
.assert_justified_epoch(0)
.assert_best_justified_epoch(0)
.apply_blocks(1)
.await
.assert_justified_epoch(2)
.assert_best_justified_epoch(2);
.assert_justified_epoch(2);
}
/// - The new justified checkpoint **does not** descend from the current.
@ -583,8 +575,7 @@ async fn justified_checkpoint_updates_with_non_descendent_inside_safe_slots_with
.unwrap();
})
.await
.assert_justified_epoch(3)
.assert_best_justified_epoch(3);
.assert_justified_epoch(3);
}
/// - The new justified checkpoint **does not** descend from the current.
@ -612,8 +603,9 @@ async fn justified_checkpoint_updates_with_non_descendent_outside_safe_slots_wit
.unwrap();
})
.await
.assert_justified_epoch(2)
.assert_best_justified_epoch(3);
// Now that `SAFE_SLOTS_TO_UPDATE_JUSTIFIED` has been removed, the new
// block should have updated the justified checkpoint.
.assert_justified_epoch(3);
}
/// - The new justified checkpoint **does not** descend from the current.
@ -641,8 +633,7 @@ async fn justified_checkpoint_updates_with_non_descendent_outside_safe_slots_wit
.unwrap();
})
.await
.assert_justified_epoch(3)
.assert_best_justified_epoch(3);
.assert_justified_epoch(3);
}
/// Check that the balances are obtained correctly.

View File

@ -3,7 +3,6 @@ mod ffg_updates;
mod no_votes;
mod votes;
use crate::proto_array::CountUnrealizedFull;
use crate::proto_array_fork_choice::{Block, ExecutionStatus, ProtoArrayForkChoice};
use crate::{InvalidationOperation, JustifiedBalances};
use serde_derive::{Deserialize, Serialize};
@ -88,7 +87,6 @@ impl ForkChoiceTestDefinition {
junk_shuffling_id.clone(),
junk_shuffling_id,
ExecutionStatus::Optimistic(ExecutionBlockHash::zero()),
CountUnrealizedFull::default(),
)
.expect("should create fork choice struct");
let equivocating_indices = BTreeSet::new();
@ -307,8 +305,8 @@ fn get_checkpoint(i: u64) -> Checkpoint {
fn check_bytes_round_trip(original: &ProtoArrayForkChoice) {
let bytes = original.as_bytes();
let decoded = ProtoArrayForkChoice::from_bytes(&bytes, CountUnrealizedFull::default())
.expect("fork choice should decode from bytes");
let decoded =
ProtoArrayForkChoice::from_bytes(&bytes).expect("fork choice should decode from bytes");
assert!(
*original == decoded,
"fork choice should encode and decode without change"

View File

@ -24,7 +24,7 @@ impl JustifiedBalances {
.validators()
.iter()
.map(|validator| {
if validator.is_active_at(current_epoch) {
if !validator.slashed && validator.is_active_at(current_epoch) {
total_effective_balance.safe_add_assign(validator.effective_balance)?;
num_active_validators.safe_add_assign(1)?;

View File

@ -6,9 +6,7 @@ mod proto_array_fork_choice;
mod ssz_container;
pub use crate::justified_balances::JustifiedBalances;
pub use crate::proto_array::{
calculate_committee_fraction, CountUnrealizedFull, InvalidationOperation,
};
pub use crate::proto_array::{calculate_committee_fraction, InvalidationOperation};
pub use crate::proto_array_fork_choice::{
Block, DoNotReOrg, ExecutionStatus, ProposerHeadError, ProposerHeadInfo, ProtoArrayForkChoice,
ReOrgThreshold,

View File

@ -118,24 +118,6 @@ impl Default for ProposerBoost {
}
}
/// Indicate whether we should strictly count unrealized justification/finalization votes.
#[derive(Default, PartialEq, Eq, Debug, Serialize, Deserialize, Copy, Clone)]
pub enum CountUnrealizedFull {
True,
#[default]
False,
}
impl From<bool> for CountUnrealizedFull {
fn from(b: bool) -> Self {
if b {
CountUnrealizedFull::True
} else {
CountUnrealizedFull::False
}
}
}
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
pub struct ProtoArray {
/// Do not attempt to prune the tree unless it has at least this many nodes. Small prunes
@ -146,7 +128,6 @@ pub struct ProtoArray {
pub nodes: Vec<ProtoNode>,
pub indices: HashMap<Hash256, usize>,
pub previous_proposer_boost: ProposerBoost,
pub count_unrealized_full: CountUnrealizedFull,
}
impl ProtoArray {
@ -684,9 +665,9 @@ impl ProtoArray {
start_root: *justified_root,
justified_checkpoint: self.justified_checkpoint,
finalized_checkpoint: self.finalized_checkpoint,
head_root: justified_node.root,
head_justified_checkpoint: justified_node.justified_checkpoint,
head_finalized_checkpoint: justified_node.finalized_checkpoint,
head_root: best_node.root,
head_justified_checkpoint: best_node.justified_checkpoint,
head_finalized_checkpoint: best_node.finalized_checkpoint,
})));
}
@ -900,55 +881,44 @@ impl ProtoArray {
}
let genesis_epoch = Epoch::new(0);
let checkpoint_match_predicate =
|node_justified_checkpoint: Checkpoint, node_finalized_checkpoint: Checkpoint| {
let correct_justified = node_justified_checkpoint == self.justified_checkpoint
|| self.justified_checkpoint.epoch == genesis_epoch;
let correct_finalized = node_finalized_checkpoint == self.finalized_checkpoint
|| self.finalized_checkpoint.epoch == genesis_epoch;
correct_justified && correct_finalized
let current_epoch = current_slot.epoch(E::slots_per_epoch());
let node_epoch = node.slot.epoch(E::slots_per_epoch());
let node_justified_checkpoint =
if let Some(justified_checkpoint) = node.justified_checkpoint {
justified_checkpoint
} else {
// The node does not have any information about the justified
// checkpoint. This indicates an inconsistent proto-array.
return false;
};
if let (
Some(unrealized_justified_checkpoint),
Some(unrealized_finalized_checkpoint),
Some(justified_checkpoint),
Some(finalized_checkpoint),
) = (
node.unrealized_justified_checkpoint,
node.unrealized_finalized_checkpoint,
node.justified_checkpoint,
node.finalized_checkpoint,
) {
let current_epoch = current_slot.epoch(E::slots_per_epoch());
// If previous epoch is justified, pull up all tips to at least the previous epoch
if CountUnrealizedFull::True == self.count_unrealized_full
&& (current_epoch > genesis_epoch
&& self.justified_checkpoint.epoch + 1 == current_epoch)
{
unrealized_justified_checkpoint.epoch + 1 >= current_epoch
// If previous epoch is not justified, pull up only tips from past epochs up to the current epoch
} else {
// If block is from a previous epoch, filter using unrealized justification & finalization information
if node.slot.epoch(E::slots_per_epoch()) < current_epoch {
checkpoint_match_predicate(
unrealized_justified_checkpoint,
unrealized_finalized_checkpoint,
)
// If block is from the current epoch, filter using the head state's justification & finalization information
} else {
checkpoint_match_predicate(justified_checkpoint, finalized_checkpoint)
}
}
} else if let (Some(justified_checkpoint), Some(finalized_checkpoint)) =
(node.justified_checkpoint, node.finalized_checkpoint)
{
checkpoint_match_predicate(justified_checkpoint, finalized_checkpoint)
let voting_source = if current_epoch > node_epoch {
// The block is from a prior epoch, the voting source will be pulled-up.
node.unrealized_justified_checkpoint
// Sometimes we don't track the unrealized justification. In
// that case, just use the fully-realized justified checkpoint.
.unwrap_or(node_justified_checkpoint)
} else {
false
// The block is not from a prior epoch, therefore the voting source
// is not pulled up.
node_justified_checkpoint
};
let mut correct_justified = self.justified_checkpoint.epoch == genesis_epoch
|| voting_source.epoch == self.justified_checkpoint.epoch;
if let Some(node_unrealized_justified_checkpoint) = node.unrealized_justified_checkpoint {
if !correct_justified && self.justified_checkpoint.epoch + 1 == current_epoch {
correct_justified = node_unrealized_justified_checkpoint.epoch
>= self.justified_checkpoint.epoch
&& voting_source.epoch + 2 >= current_epoch;
}
}
let correct_finalized = self.finalized_checkpoint.epoch == genesis_epoch
|| self.is_finalized_checkpoint_or_descendant::<E>(node.root);
correct_justified && correct_finalized
}
/// Return a reverse iterator over the nodes which comprise the chain ending at `block_root`.

View File

@ -1,8 +1,8 @@
use crate::{
error::Error,
proto_array::{
calculate_committee_fraction, CountUnrealizedFull, InvalidationOperation, Iter,
ProposerBoost, ProtoArray, ProtoNode,
calculate_committee_fraction, InvalidationOperation, Iter, ProposerBoost, ProtoArray,
ProtoNode,
},
ssz_container::SszContainer,
JustifiedBalances,
@ -307,7 +307,6 @@ impl ProtoArrayForkChoice {
current_epoch_shuffling_id: AttestationShufflingId,
next_epoch_shuffling_id: AttestationShufflingId,
execution_status: ExecutionStatus,
count_unrealized_full: CountUnrealizedFull,
) -> Result<Self, String> {
let mut proto_array = ProtoArray {
prune_threshold: DEFAULT_PRUNE_THRESHOLD,
@ -316,7 +315,6 @@ impl ProtoArrayForkChoice {
nodes: Vec::with_capacity(1),
indices: HashMap::with_capacity(1),
previous_proposer_boost: ProposerBoost::default(),
count_unrealized_full,
};
let block = Block {
@ -780,13 +778,10 @@ impl ProtoArrayForkChoice {
SszContainer::from(self).as_ssz_bytes()
}
pub fn from_bytes(
bytes: &[u8],
count_unrealized_full: CountUnrealizedFull,
) -> Result<Self, String> {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
let container = SszContainer::from_ssz_bytes(bytes)
.map_err(|e| format!("Failed to decode ProtoArrayForkChoice: {:?}", e))?;
(container, count_unrealized_full)
container
.try_into()
.map_err(|e| format!("Failed to initialize ProtoArrayForkChoice: {e:?}"))
}
@ -950,7 +945,6 @@ mod test_compute_deltas {
junk_shuffling_id.clone(),
junk_shuffling_id.clone(),
execution_status,
CountUnrealizedFull::default(),
)
.unwrap();
@ -1076,7 +1070,6 @@ mod test_compute_deltas {
junk_shuffling_id.clone(),
junk_shuffling_id.clone(),
execution_status,
CountUnrealizedFull::default(),
)
.unwrap();

View File

@ -1,6 +1,6 @@
use crate::proto_array::ProposerBoost;
use crate::{
proto_array::{CountUnrealizedFull, ProtoArray, ProtoNode},
proto_array::{ProtoArray, ProtoNode},
proto_array_fork_choice::{ElasticList, ProtoArrayForkChoice, VoteTracker},
Error, JustifiedBalances,
};
@ -43,12 +43,10 @@ impl From<&ProtoArrayForkChoice> for SszContainer {
}
}
impl TryFrom<(SszContainer, CountUnrealizedFull)> for ProtoArrayForkChoice {
impl TryFrom<SszContainer> for ProtoArrayForkChoice {
type Error = Error;
fn try_from(
(from, count_unrealized_full): (SszContainer, CountUnrealizedFull),
) -> Result<Self, Error> {
fn try_from(from: SszContainer) -> Result<Self, Error> {
let proto_array = ProtoArray {
prune_threshold: from.prune_threshold,
justified_checkpoint: from.justified_checkpoint,
@ -56,7 +54,6 @@ impl TryFrom<(SszContainer, CountUnrealizedFull)> for ProtoArrayForkChoice {
nodes: from.nodes,
indices: from.indices.into_iter().collect::<HashMap<_, _>>(),
previous_proposer_boost: from.previous_proposer_boost,
count_unrealized_full,
};
Ok(Self {

View File

@ -633,7 +633,7 @@ impl ChainSpec {
* Capella hard fork params
*/
capella_fork_version: [0x03, 00, 00, 00],
capella_fork_epoch: None,
capella_fork_epoch: Some(Epoch::new(194048)),
max_validators_per_withdrawals_sweep: 16384,
/*

View File

@ -171,3 +171,13 @@ impl<T: EthSpec> ForkVersionDeserialize for ExecutionPayload<T> {
})
}
}
impl<T: EthSpec> ExecutionPayload<T> {
pub fn fork_name(&self) -> ForkName {
match self {
ExecutionPayload::Merge(_) => ForkName::Merge,
ExecutionPayload::Capella(_) => ForkName::Capella,
ExecutionPayload::Eip4844(_) => ForkName::Eip4844,
}
}
}

View File

@ -1,7 +1,7 @@
[package]
name = "lcli"
description = "Lighthouse CLI (modeled after zcli)"
version = "3.5.1"
version = "4.0.1-rc.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2021"

View File

@ -1,6 +1,6 @@
[package]
name = "lighthouse"
version = "3.5.1"
version = "4.0.1-rc.0"
authors = ["Sigma Prime <contact@sigmaprime.io>"]
edition = "2021"
autotests = false

View File

@ -1,4 +1,4 @@
use beacon_node::{beacon_chain::CountUnrealizedFull, ClientConfig as Config};
use beacon_node::ClientConfig as Config;
use crate::exec::{CommandLineTestExec, CompletedTest};
use beacon_node::beacon_chain::chain_config::{
@ -118,6 +118,26 @@ fn disable_lock_timeouts_flag() {
.with_config(|config| assert!(!config.chain.enable_lock_timeouts));
}
#[test]
fn shuffling_cache_default() {
CommandLineTest::new()
.run_with_zero_port()
.with_config(|config| {
assert_eq!(
config.chain.shuffling_cache_size,
beacon_node::beacon_chain::shuffling_cache::DEFAULT_CACHE_SIZE
)
});
}
#[test]
fn shuffling_cache_set() {
CommandLineTest::new()
.flag("shuffling-cache-size", Some("500"))
.run_with_zero_port()
.with_config(|config| assert_eq!(config.chain.shuffling_cache_size, 500));
}
#[test]
fn fork_choice_before_proposal_timeout_default() {
CommandLineTest::new()
@ -212,74 +232,58 @@ fn paranoid_block_proposal_on() {
.with_config(|config| assert!(config.chain.paranoid_block_proposal));
}
#[test]
fn count_unrealized_default() {
CommandLineTest::new()
.run_with_zero_port()
.with_config(|config| assert!(config.chain.count_unrealized));
}
#[test]
fn count_unrealized_no_arg() {
CommandLineTest::new()
.flag("count-unrealized", None)
.run_with_zero_port()
.with_config(|config| assert!(config.chain.count_unrealized));
// This flag should be ignored, so there's nothing to test but that the
// client starts with the flag present.
.run_with_zero_port();
}
#[test]
fn count_unrealized_false() {
CommandLineTest::new()
.flag("count-unrealized", Some("false"))
.run_with_zero_port()
.with_config(|config| assert!(!config.chain.count_unrealized));
// This flag should be ignored, so there's nothing to test but that the
// client starts with the flag present.
.run_with_zero_port();
}
#[test]
fn count_unrealized_true() {
CommandLineTest::new()
.flag("count-unrealized", Some("true"))
.run_with_zero_port()
.with_config(|config| assert!(config.chain.count_unrealized));
// This flag should be ignored, so there's nothing to test but that the
// client starts with the flag present.
.run_with_zero_port();
}
#[test]
fn count_unrealized_full_no_arg() {
CommandLineTest::new()
.flag("count-unrealized-full", None)
.run_with_zero_port()
.with_config(|config| {
assert_eq!(
config.chain.count_unrealized_full,
CountUnrealizedFull::False
)
});
// This flag should be ignored, so there's nothing to test but that the
// client starts with the flag present.
.run_with_zero_port();
}
#[test]
fn count_unrealized_full_false() {
CommandLineTest::new()
.flag("count-unrealized-full", Some("false"))
.run_with_zero_port()
.with_config(|config| {
assert_eq!(
config.chain.count_unrealized_full,
CountUnrealizedFull::False
)
});
// This flag should be ignored, so there's nothing to test but that the
// client starts with the flag present.
.run_with_zero_port();
}
#[test]
fn count_unrealized_full_true() {
CommandLineTest::new()
.flag("count-unrealized-full", Some("true"))
.run_with_zero_port()
.with_config(|config| {
assert_eq!(
config.chain.count_unrealized_full,
CountUnrealizedFull::True
)
});
// This flag should be ignored, so there's nothing to test but that the
// client starts with the flag present.
.run_with_zero_port();
}
#[test]

View File

@ -1,4 +1,4 @@
TESTS_TAG := v1.3.0-rc.1
TESTS_TAG := v1.3.0-rc.1 # FIXME: move to latest
TESTS = general minimal mainnet
TARBALLS = $(patsubst %,%-$(TESTS_TAG).tar.gz,$(TESTS))

View File

@ -45,7 +45,6 @@ pub struct Checks {
justified_checkpoint: Option<Checkpoint>,
justified_checkpoint_root: Option<Hash256>,
finalized_checkpoint: Option<Checkpoint>,
best_justified_checkpoint: Option<Checkpoint>,
u_justified_checkpoint: Option<Checkpoint>,
u_finalized_checkpoint: Option<Checkpoint>,
proposer_boost_root: Option<Hash256>,
@ -229,7 +228,6 @@ impl<E: EthSpec> Case for ForkChoiceTest<E> {
justified_checkpoint,
justified_checkpoint_root,
finalized_checkpoint,
best_justified_checkpoint,
u_justified_checkpoint,
u_finalized_checkpoint,
proposer_boost_root,
@ -260,11 +258,6 @@ impl<E: EthSpec> Case for ForkChoiceTest<E> {
tester.check_finalized_checkpoint(*expected_finalized_checkpoint)?;
}
if let Some(expected_best_justified_checkpoint) = best_justified_checkpoint {
tester
.check_best_justified_checkpoint(*expected_best_justified_checkpoint)?;
}
if let Some(expected_u_justified_checkpoint) = u_justified_checkpoint {
tester.check_u_justified_checkpoint(*expected_u_justified_checkpoint)?;
}
@ -378,7 +371,7 @@ impl<E: EthSpec> Tester<E> {
.chain
.canonical_head
.fork_choice_write_lock()
.update_time(slot, &self.spec)
.update_time(slot)
.unwrap();
}
@ -388,7 +381,7 @@ impl<E: EthSpec> Tester<E> {
let result = self.block_on_dangerous(self.harness.chain.process_block(
block_root,
block.clone(),
CountUnrealized::False,
CountUnrealized::True,
NotifyExecutionLayer::Yes,
))?;
if result.is_ok() != valid {
@ -448,7 +441,7 @@ impl<E: EthSpec> Tester<E> {
&state,
PayloadVerificationStatus::Irrelevant,
&self.harness.chain.spec,
self.harness.chain.config.count_unrealized.into(),
CountUnrealized::True,
);
if result.is_ok() {
@ -576,23 +569,6 @@ impl<E: EthSpec> Tester<E> {
check_equal("finalized_checkpoint", fc_checkpoint, expected_checkpoint)
}
pub fn check_best_justified_checkpoint(
&self,
expected_checkpoint: Checkpoint,
) -> Result<(), Error> {
let best_justified_checkpoint = self
.harness
.chain
.canonical_head
.fork_choice_read_lock()
.best_justified_checkpoint();
check_equal(
"best_justified_checkpoint",
best_justified_checkpoint,
expected_checkpoint,
)
}
pub fn check_u_justified_checkpoint(
&self,
expected_checkpoint: Checkpoint,

View File

@ -555,6 +555,11 @@ impl<E: EthSpec + TypeName> Handler for ForkChoiceHandler<E> {
return false;
}
// Tests are no longer generated for the base/phase0 specification.
if fork_name == ForkName::Base {
return false;
}
// These tests check block validity (which may include signatures) and there is no need to
// run them with fake crypto.
cfg!(not(feature = "fake_crypto"))

View File

@ -528,6 +528,18 @@ fn fork_choice_ex_ante() {
ForkChoiceHandler::<MainnetEthSpec>::new("ex_ante").run();
}
#[test]
fn fork_choice_reorg() {
ForkChoiceHandler::<MinimalEthSpec>::new("reorg").run();
// There is no mainnet variant for this test.
}
#[test]
fn fork_choice_withholding() {
ForkChoiceHandler::<MinimalEthSpec>::new("withholding").run();
// There is no mainnet variant for this test.
}
#[test]
fn optimistic_sync() {
OptimisticSyncHandler::<MinimalEthSpec>::default().run();

View File

@ -11,7 +11,7 @@ use unused_port::unused_tcp4_port;
/// We've pinned the Nethermind version since our method of using the `master` branch to
/// find the latest tag isn't working. It appears Nethermind don't always tag on `master`.
/// We should fix this so we always pull the latest version of Nethermind.
const NETHERMIND_BRANCH: &str = "release/1.14.6";
const NETHERMIND_BRANCH: &str = "release/1.17.1";
const NETHERMIND_REPO_URL: &str = "https://github.com/NethermindEth/nethermind";
fn build_result(repo_dir: &Path) -> Output {
@ -67,7 +67,7 @@ impl NethermindEngine {
.join("Nethermind.Runner")
.join("bin")
.join("Release")
.join("net6.0")
.join("net7.0")
.join("Nethermind.Runner")
}
}
@ -95,7 +95,7 @@ impl GenericExecutionEngine for NethermindEngine {
.arg("--datadir")
.arg(datadir.path().to_str().unwrap())
.arg("--config")
.arg("kiln")
.arg("hive")
.arg("--Init.ChainSpecPath")
.arg(genesis_json_path.to_str().unwrap())
.arg("--Merge.TerminalTotalDifficulty")

View File

@ -15,8 +15,8 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use task_executor::TaskExecutor;
use tokio::time::sleep;
use types::{
Address, ChainSpec, EthSpec, ExecutionBlockHash, ExecutionPayload, ForkName, FullPayload,
Hash256, MainnetEthSpec, PublicKeyBytes, Slot, Uint256,
Address, ChainSpec, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadHeader,
ForkName, FullPayload, Hash256, MainnetEthSpec, PublicKeyBytes, Slot, Uint256,
};
const EXECUTION_ENGINE_START_TIMEOUT: Duration = Duration::from_secs(30);
@ -628,12 +628,32 @@ async fn check_payload_reconstruction<E: GenericExecutionEngine>(
) {
let reconstructed = ee
.execution_layer
// FIXME: handle other forks here?
.get_payload_by_block_hash(payload.block_hash(), ForkName::Merge)
.get_payload_by_block_hash(payload.block_hash(), payload.fork_name())
.await
.unwrap()
.unwrap();
assert_eq!(reconstructed, *payload);
// also check via payload bodies method
let capabilities = ee
.execution_layer
.get_engine_capabilities(None)
.await
.unwrap();
assert!(
// if the engine doesn't have these capabilities, we need to update the client in our tests
capabilities.get_payload_bodies_by_hash_v1 && capabilities.get_payload_bodies_by_range_v1,
"Testing engine does not support payload bodies methods"
);
let mut bodies = ee
.execution_layer
.get_payload_bodies_by_hash(vec![payload.block_hash()])
.await
.unwrap();
assert_eq!(bodies.len(), 1);
let body = bodies.pop().unwrap().unwrap();
let header = ExecutionPayloadHeader::from(payload.to_ref());
let reconstructed_from_body = body.to_payload(header).unwrap();
assert_eq!(reconstructed_from_body, *payload);
}
/// Returns the duration since the unix epoch.