Merge branch 'eip4844' into deneb-free-blobs
This commit is contained in:
commit
25a2d8f078
54
Cargo.lock
generated
54
Cargo.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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" }
|
||||
|
974
beacon_node/beacon_chain/src/beacon_block_streamer.rs
Normal file
974
beacon_node/beacon_chain/src/beacon_block_streamer.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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(),
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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))?;
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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])
|
||||
}
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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 =
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
@ -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")
|
||||
)
|
||||
|
@ -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");
|
||||
|
@ -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.
|
||||
//
|
||||
|
@ -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
|
||||
|
@ -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?
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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};
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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};
|
||||
|
@ -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.
|
||||
|
@ -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"
|
||||
|
@ -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)?;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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`.
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
||||
/*
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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"))
|
||||
|
@ -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();
|
||||
|
@ -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")
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user