diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index c1cf330451..500f718e8a 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -470,6 +470,8 @@ pub struct BeaconChain { pub(crate) attester_cache: Arc, /// A cache used when producing attestations whilst the head block is still being imported. pub early_attester_cache: EarlyAttesterCache, + /// A cache used to store verified/equivocating inclusion lists. + pub inclusion_list_cache: InclusionListCache, /// Cache gossip verified blocks to serve over ReqResp before they are imported pub reqresp_pre_import_cache: Arc>>, /// A cache used to keep track of various block timings. diff --git a/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs b/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs index a6aedda19d..318de20c01 100644 --- a/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs +++ b/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs @@ -10,7 +10,7 @@ use fork_choice::ForkChoiceStore; use proto_array::JustifiedBalances; use safe_arith::ArithError; use ssz_derive::{Decode, Encode}; -use std::collections::BTreeSet; +use std::collections::{BTreeSet, HashMap}; use std::marker::PhantomData; use std::sync::Arc; use store::{Error as StoreError, HotColdDB, ItemStore}; @@ -140,6 +140,7 @@ pub struct BeaconForkChoiceStore, Cold: ItemStore< unrealized_finalized_checkpoint: Checkpoint, proposer_boost_root: Hash256, equivocating_indices: BTreeSet, + inclusion_list_equivocators: HashMap<(Slot, Hash256), BTreeSet>, _phantom: PhantomData, } @@ -189,6 +190,7 @@ where unrealized_finalized_checkpoint: finalized_checkpoint, proposer_boost_root: Hash256::zero(), equivocating_indices: BTreeSet::new(), + inclusion_list_equivocators: HashMap::new(), _phantom: PhantomData, }) } @@ -227,6 +229,7 @@ where unrealized_finalized_checkpoint: persisted.unrealized_finalized_checkpoint, proposer_boost_root: persisted.proposer_boost_root, equivocating_indices: persisted.equivocating_indices, + inclusion_list_equivocators: HashMap::new(), _phantom: PhantomData, }) } diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index 9d99ff9d8e..d1088b849c 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -965,6 +965,7 @@ where validator_pubkey_cache: RwLock::new(validator_pubkey_cache), attester_cache: <_>::default(), early_attester_cache: <_>::default(), + inclusion_list_cache: <_>::default(), reqresp_pre_import_cache: <_>::default(), light_client_server_cache: LightClientServerCache::new(), light_client_server_tx: self.light_client_server_tx, diff --git a/beacon_node/beacon_chain/src/execution_payload.rs b/beacon_node/beacon_chain/src/execution_payload.rs index 92d24c53c0..e5e49d4472 100644 --- a/beacon_node/beacon_chain/src/execution_payload.rs +++ b/beacon_node/beacon_chain/src/execution_payload.rs @@ -52,6 +52,7 @@ pub enum NotifyExecutionLayer { pub struct PayloadNotifier { pub chain: Arc>, pub block: Arc>, + pub inclusion_list_transactions: InclusionListTransactions, payload_verification_status: Option, } @@ -102,10 +103,16 @@ impl PayloadNotifier { Some(PayloadVerificationStatus::Irrelevant) }; + let inclusion_list_transactions = chain + .inclusion_list_cache + .get_inclusion_list_transactions(block.slot()) + .unwrap_or(vec![].into()); + Ok(Self { chain, block, payload_verification_status, + inclusion_list_transactions, }) } @@ -113,7 +120,12 @@ impl PayloadNotifier { if let Some(precomputed_status) = self.payload_verification_status { Ok(precomputed_status) } else { - notify_new_payload(&self.chain, self.block.message()).await + notify_new_payload( + &self.chain, + self.block.message(), + self.inclusion_list_transactions, + ) + .await } } } @@ -130,6 +142,7 @@ impl PayloadNotifier { async fn notify_new_payload<'a, T: BeaconChainTypes>( chain: &Arc>, block: BeaconBlockRef<'a, T::EthSpec>, + il_transactions: InclusionListTransactions, ) -> Result { let execution_layer = chain .execution_layer @@ -137,7 +150,12 @@ async fn notify_new_payload<'a, T: BeaconChainTypes>( .ok_or(ExecutionPayloadError::NoExecutionConnection)?; let execution_block_hash = block.execution_payload()?.block_hash(); - let new_payload_response = execution_layer.notify_new_payload(block.try_into()?).await; + let new_payload_response = execution_layer + .notify_new_payload(NewPayloadRequest::try_from_block_and_il_transactions( + block, + il_transactions, + )?) + .await; match new_payload_response { Ok(status) => match status { diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index 468a5d7196..5d22be9afd 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -724,6 +724,8 @@ impl HttpJsonRpc { .await } + pub async fn update_payload_with_inclusion_list(&self) {} + pub async fn get_inclusion_list( &self, parent_hash: Hash256, diff --git a/beacon_node/execution_layer/src/engine_api/new_payload_request.rs b/beacon_node/execution_layer/src/engine_api/new_payload_request.rs index 60bc848974..b3dc84d253 100644 --- a/beacon_node/execution_layer/src/engine_api/new_payload_request.rs +++ b/beacon_node/execution_layer/src/engine_api/new_payload_request.rs @@ -5,7 +5,7 @@ use state_processing::per_block_processing::deneb::kzg_commitment_to_versioned_h use superstruct::superstruct; use types::{ BeaconBlockRef, BeaconStateError, EthSpec, ExecutionBlockHash, ExecutionPayload, - ExecutionPayloadRef, Hash256, VersionedHash, + ExecutionPayloadRef, Hash256, InclusionListTransactions, VersionedHash, }; use types::{ ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, @@ -45,6 +45,8 @@ pub struct NewPayloadRequest<'block, E: EthSpec> { pub parent_beacon_block_root: Hash256, #[superstruct(only(Electra))] pub execution_requests: &'block ExecutionRequests, + #[superstruct(only(Electra))] + pub il_transactions: InclusionListTransactions, } impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { @@ -153,13 +155,14 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { } } -impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> { - type Error = BeaconStateError; - - fn try_from(block: BeaconBlockRef<'a, E>) -> Result { +impl<'a, E: EthSpec> NewPayloadRequest<'a, E> { + pub fn try_from_block_and_il_transactions( + block: BeaconBlockRef<'a, E>, + il_transactions: InclusionListTransactions, + ) -> Result { match block { BeaconBlockRef::Base(_) | BeaconBlockRef::Altair(_) => { - Err(Self::Error::IncorrectStateVariant) + Err(BeaconStateError::IncorrectStateVariant) } BeaconBlockRef::Bellatrix(block_ref) => { Ok(Self::Bellatrix(NewPayloadRequestBellatrix { @@ -189,6 +192,50 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> .collect(), parent_beacon_block_root: block_ref.parent_root, execution_requests: &block_ref.body.execution_requests, + il_transactions, + })), + } + } +} + +impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> { + type Error = BeaconStateError; + + fn try_from(block: BeaconBlockRef<'a, E>) -> Result { + match block { + BeaconBlockRef::Base(_) | BeaconBlockRef::Altair(_) => { + Err(Self::Error::IncorrectStateVariant) + } + BeaconBlockRef::Bellatrix(block_ref) => { + Ok(Self::Bellatrix(NewPayloadRequestBellatrix { + execution_payload: &block_ref.body.execution_payload.execution_payload, + })) + } + BeaconBlockRef::Capella(block_ref) => Ok(Self::Capella(NewPayloadRequestCapella { + execution_payload: &block_ref.body.execution_payload.execution_payload, + })), + BeaconBlockRef::Deneb(block_ref) => Ok(Self::Deneb(NewPayloadRequestDeneb { + execution_payload: &block_ref.body.execution_payload.execution_payload, + versioned_hashes: block_ref + .body + .blob_kzg_commitments + .iter() + .map(kzg_commitment_to_versioned_hash) + .collect(), + parent_beacon_block_root: block_ref.parent_root, + })), + // TODO(focil) need to clean up this conversion + BeaconBlockRef::Electra(block_ref) => Ok(Self::Electra(NewPayloadRequestElectra { + execution_payload: &block_ref.body.execution_payload.execution_payload, + versioned_hashes: block_ref + .body + .blob_kzg_commitments + .iter() + .map(kzg_commitment_to_versioned_hash) + .collect(), + parent_beacon_block_root: block_ref.parent_root, + execution_requests: &block_ref.body.execution_requests, + il_transactions: vec![].into(), })), } } diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index b306141215..41f5baa67b 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -1384,6 +1384,7 @@ impl ExecutionLayer { ); } *self.inner.last_new_payload_errored.write().await = result.is_err(); + // TODO(focil) write block hash to some store in the case where theres an IL valdation error on newPayloadv5 process_payload_status(block_hash, result, self.log()) .map_err(Box::new) diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index c3e9a91066..e78febaf6c 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -28,6 +28,7 @@ pub use self::committee_cache::{ }; pub use crate::beacon_state::balance::Balance; pub use crate::beacon_state::exit_cache::ExitCache; +pub use crate::beacon_state::inclusion_list_cache::InclusionListCache; pub use crate::beacon_state::progressive_balances_cache::*; pub use crate::beacon_state::slashings_cache::SlashingsCache; pub use eth_spec::*; @@ -38,6 +39,7 @@ pub use milhouse::{interface::Interface, List, Vector}; mod committee_cache; mod balance; mod exit_cache; +mod inclusion_list_cache; mod iter; mod progressive_balances_cache; mod pubkey_cache; diff --git a/consensus/types/src/beacon_state/inclusion_list_cache.rs b/consensus/types/src/beacon_state/inclusion_list_cache.rs new file mode 100644 index 0000000000..182189b9f2 --- /dev/null +++ b/consensus/types/src/beacon_state/inclusion_list_cache.rs @@ -0,0 +1,91 @@ +use std::collections::{HashMap, HashSet}; + +use super::{EthSpec, InclusionListTransactions, SignedInclusionList, Slot, Transaction}; + +/// Map from slot to inclusion lists +#[derive(Debug, Default, Clone, PartialEq)] +pub struct InclusionListCache { + inner_map: HashMap>, +} + +type ValidatorIndex = u64; + +#[derive(Debug, Default, Clone, PartialEq)] +struct Inner { + pub inclusion_lists: HashSet>, + pub inclusion_lists_seen: HashSet, + pub inclusion_list_equivocators: HashSet, + pub inclusion_list_transactions: HashSet>, +} + +impl InclusionListCache { + pub fn initialize(&mut self, slot: Slot) { + let inner = Inner { + inclusion_lists: HashSet::new(), + inclusion_lists_seen: HashSet::new(), + inclusion_list_equivocators: HashSet::new(), + inclusion_list_transactions: HashSet::new(), + }; + + self.inner_map.insert(slot, inner); + } + + pub fn clear_cache(&mut self, slot: Slot) { + self.inner_map.remove(&slot); + } + + pub fn on_inclusion_list(&mut self, inclusion_list: SignedInclusionList) { + let Some(inner) = self.inner_map.get_mut(&inclusion_list.message.slot) else { + return; + }; + + if inner + .inclusion_list_equivocators + .contains(&inclusion_list.message.validator_index) + { + return; + } + + if inner.inclusion_lists_seen.contains(&inclusion_list.message.validator_index) && !inner.inclusion_lists.contains(&inclusion_list) { + inner + .inclusion_list_equivocators + .insert(inclusion_list.message.validator_index); + return; + } + + // Skip inserting into the cache if we've already seen an identical IL + if inner.inclusion_lists_seen.contains(&inclusion_list.message.validator_index) && inner.inclusion_lists.contains(&inclusion_list) { + return; + } + + for transaction in &inclusion_list.message.transactions { + inner + .inclusion_list_transactions + .insert(transaction.clone()); + } + inner.inclusion_lists_seen.insert(inclusion_list.message.validator_index); + inner.inclusion_lists.insert(inclusion_list); + } + + pub fn get_inclusion_list_transactions( + &self, + slot: Slot, + ) -> Option> { + let Some(inner) = &self.inner_map.get(&slot) else { + return None; + }; + + let il = inner + .inclusion_list_transactions + .iter() + .cloned() + .collect::>(); + Some(il.into()) + } +} + +impl arbitrary::Arbitrary<'_> for InclusionListCache { + fn arbitrary(_u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { + Ok(Self::default()) + } +}