From 9769a247b23b214d6d0dde843d6d9fb8d2be95f5 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Fri, 6 Oct 2023 03:05:47 +0000 Subject: [PATCH 01/21] Address Clippy 1.73 lints (#4809) ## Proposed Changes Fix Clippy lints enabled by default in Rust 1.73.0, released today. --- .../beacon_chain/src/eth1_finalization_cache.rs | 2 +- .../src/test_utils/execution_block_generator.rs | 2 +- beacon_node/http_api/src/standard_block_rewards.rs | 4 ++-- beacon_node/lighthouse_network/src/peer_manager/mod.rs | 2 +- .../src/peer_manager/peerdb/score.rs | 6 ++++-- beacon_node/operation_pool/src/attestation_storage.rs | 10 ++-------- beacon_node/store/src/memory_store.rs | 2 +- validator_client/src/attestation_service.rs | 2 +- validator_client/src/duties_service/sync.rs | 2 +- 9 files changed, 14 insertions(+), 18 deletions(-) diff --git a/beacon_node/beacon_chain/src/eth1_finalization_cache.rs b/beacon_node/beacon_chain/src/eth1_finalization_cache.rs index 7cf805a126d..e640e8e51e6 100644 --- a/beacon_node/beacon_chain/src/eth1_finalization_cache.rs +++ b/beacon_node/beacon_chain/src/eth1_finalization_cache.rs @@ -66,7 +66,7 @@ impl CheckpointMap { pub fn insert(&mut self, checkpoint: Checkpoint, eth1_finalization_data: Eth1FinalizationData) { self.store .entry(checkpoint.epoch) - .or_insert_with(Vec::new) + .or_default() .push((checkpoint.root, eth1_finalization_data)); // faster to reduce size after the fact than do pre-checking to see diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index a8d98a767fb..d76d54bc7dc 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -343,7 +343,7 @@ impl ExecutionBlockGenerator { let block_hash = block.block_hash(); self.block_hashes .entry(block.block_number()) - .or_insert_with(Vec::new) + .or_default() .push(block_hash); self.blocks.insert(block_hash, block); diff --git a/beacon_node/http_api/src/standard_block_rewards.rs b/beacon_node/http_api/src/standard_block_rewards.rs index de7e5eb7d3b..97e5a87fd3a 100644 --- a/beacon_node/http_api/src/standard_block_rewards.rs +++ b/beacon_node/http_api/src/standard_block_rewards.rs @@ -5,8 +5,8 @@ use beacon_chain::{BeaconChain, BeaconChainTypes}; use eth2::lighthouse::StandardBlockReward; use std::sync::Arc; use warp_utils::reject::beacon_chain_error; -//// The difference between block_rewards and beacon_block_rewards is the later returns block -//// reward format that satisfies beacon-api specs +/// The difference between block_rewards and beacon_block_rewards is the later returns block +/// reward format that satisfies beacon-api specs pub fn compute_beacon_block_rewards( chain: Arc>, block_id: BlockId, diff --git a/beacon_node/lighthouse_network/src/peer_manager/mod.rs b/beacon_node/lighthouse_network/src/peer_manager/mod.rs index d8470fe6f09..03e17ed255b 100644 --- a/beacon_node/lighthouse_network/src/peer_manager/mod.rs +++ b/beacon_node/lighthouse_network/src/peer_manager/mod.rs @@ -1043,7 +1043,7 @@ impl PeerManager { Subnet::Attestation(_) => { subnet_to_peer .entry(subnet) - .or_insert_with(Vec::new) + .or_default() .push((*peer_id, info.clone())); } Subnet::SyncCommittee(id) => { diff --git a/beacon_node/lighthouse_network/src/peer_manager/peerdb/score.rs b/beacon_node/lighthouse_network/src/peer_manager/peerdb/score.rs index bafa355d687..877d725812c 100644 --- a/beacon_node/lighthouse_network/src/peer_manager/peerdb/score.rs +++ b/beacon_node/lighthouse_network/src/peer_manager/peerdb/score.rs @@ -330,13 +330,15 @@ impl Eq for Score {} impl PartialOrd for Score { fn partial_cmp(&self, other: &Score) -> Option { - self.score().partial_cmp(&other.score()) + Some(self.cmp(other)) } } impl Ord for Score { fn cmp(&self, other: &Score) -> std::cmp::Ordering { - self.partial_cmp(other).unwrap_or(std::cmp::Ordering::Equal) + self.score() + .partial_cmp(&other.score()) + .unwrap_or(std::cmp::Ordering::Equal) } } diff --git a/beacon_node/operation_pool/src/attestation_storage.rs b/beacon_node/operation_pool/src/attestation_storage.rs index 0fb9bafd821..dac5e25b349 100644 --- a/beacon_node/operation_pool/src/attestation_storage.rs +++ b/beacon_node/operation_pool/src/attestation_storage.rs @@ -151,14 +151,8 @@ impl AttestationMap { indexed, } = SplitAttestation::new(attestation, attesting_indices); - let attestation_map = self - .checkpoint_map - .entry(checkpoint) - .or_insert_with(AttestationDataMap::default); - let attestations = attestation_map - .attestations - .entry(data) - .or_insert_with(Vec::new); + let attestation_map = self.checkpoint_map.entry(checkpoint).or_default(); + let attestations = attestation_map.attestations.entry(data).or_default(); // Greedily aggregate the attestation with all existing attestations. // NOTE: this is sub-optimal and in future we will remove this in favour of max-clique diff --git a/beacon_node/store/src/memory_store.rs b/beacon_node/store/src/memory_store.rs index 1473f59a4e9..3f6a9e514f2 100644 --- a/beacon_node/store/src/memory_store.rs +++ b/beacon_node/store/src/memory_store.rs @@ -48,7 +48,7 @@ impl KeyValueStore for MemoryStore { self.col_keys .write() .entry(col.as_bytes().to_vec()) - .or_insert_with(HashSet::new) + .or_default() .insert(key.to_vec()); Ok(()) } diff --git a/validator_client/src/attestation_service.rs b/validator_client/src/attestation_service.rs index 1b7b391a0c8..b5bb6702ae2 100644 --- a/validator_client/src/attestation_service.rs +++ b/validator_client/src/attestation_service.rs @@ -193,7 +193,7 @@ impl AttestationService { .into_iter() .fold(HashMap::new(), |mut map, duty_and_proof| { map.entry(duty_and_proof.duty.committee_index) - .or_insert_with(Vec::new) + .or_default() .push(duty_and_proof); map }); diff --git a/validator_client/src/duties_service/sync.rs b/validator_client/src/duties_service/sync.rs index cf63d8ac625..12623d3468a 100644 --- a/validator_client/src/duties_service/sync.rs +++ b/validator_client/src/duties_service/sync.rs @@ -163,7 +163,7 @@ impl SyncDutiesMap { committees_writer .entry(committee_period) - .or_insert_with(CommitteeDuties::default) + .or_default() .init(validator_indices); // Return shared reference From accb56e4fb8448dfcc9c38695d99ab641b2f31a2 Mon Sep 17 00:00:00 2001 From: chonghe Date: Fri, 6 Oct 2023 04:34:47 +0000 Subject: [PATCH 02/21] Revise doc API section (#4798) ## Issue Addressed Partially #4788 ## Proposed Changes Remove documentation on `/lighthouse/database/reconstruct` API to avoid confusion as the calling the API during historical block download will show an error in the beacon log Add Events API about `payload_attributes` ## Additional Info Please provide any additional information. For example, future considerations or information useful for reviewers. Co-authored-by: chonghe <44791194+chong-he@users.noreply.github.com> Co-authored-by: Michael Sproul --- book/src/api-bn.md | 16 ++++++++++++++++ book/src/api-lighthouse.md | 16 ---------------- book/src/builders.md | 3 +++ book/src/validator-inclusion.md | 2 +- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/book/src/api-bn.md b/book/src/api-bn.md index 11a006493aa..519ce57055e 100644 --- a/book/src/api-bn.md +++ b/book/src/api-bn.md @@ -126,6 +126,22 @@ curl -X GET "http://localhost:5052/eth/v1/beacon/states/head/validators/1" -H " ``` You can replace `1` in the above command with the validator index that you would like to query. Other API query can be done similarly by changing the link according to the Beacon API. +### Events API +The [events API](https://ethereum.github.io/beacon-APIs/#/Events/eventstream) provides information such as the payload attributes that are of interest to block builders and relays. To query the payload attributes, it is necessary to run Lighthouse beacon node with the flag `--always-prepare-payload`. It is also recommended to add the flag `--prepare-payload-lookahead 8000` which configures the payload attributes to be sent at 4s into each slot (or 8s from the start of the next slot). An example of the command is: + +```bash +curl -X 'GET' \ +'http://localhost:5052/eth/v1/events?topics=payload_attributes' \ +-H 'accept: text/event-stream' +``` + +An example of response is: + +```json +data:{"version":"capella","data":{"proposal_slot":"11047","proposer_index":"336057","parent_block_root":"0x26f8999d270dd4677c2a1c815361707157a531f6c599f78fa942c98b545e1799","parent_block_number":"9259","parent_block_hash":"0x7fb788cd7afa814e578afa00a3edd250cdd4c8e35c22badd327d981b5bda33d2","payload_attributes":{"timestamp":"1696034964","prev_randao":"0xeee34d7a3f6b99ade6c6a881046c9c0e96baab2ed9469102d46eb8d6e4fde14c","suggested_fee_recipient":"0x0000000000000000000000000000000000000001","withdrawals":[{"index":"40705","validator_index":"360712","address":"0x73b2e0e54510239e22cc936f0b4a6de1acf0abde","amount":"1202941"},{"index":"40706","validator_index":"360713","address":"0x73b2e0e54510239e22cc936f0b4a6de1acf0abde","amount":"1201138"},{"index":"40707","validator_index":"360714","address":"0x73b2e0e54510239e22cc936f0b4a6de1acf0abde","amount":"1215255"},{"index":"40708","validator_index":"360715","address":"0x73b2e0e54510239e22cc936f0b4a6de1acf0abde","amount":"1161977"},{"index":"40709","validator_index":"360716","address":"0x73b2e0e54510239e22cc936f0b4a6de1acf0abde","amount":"1257278"},{"index":"40710","validator_index":"360717","address":"0x73b2e0e54510239e22cc936f0b4a6de1acf0abde","amount":"1247740"},{"index":"40711","validator_index":"360718","address":"0x73b2e0e54510239e22cc936f0b4a6de1acf0abde","amount":"1204337"},{"index":"40712","validator_index":"360719","address":"0x73b2e0e54510239e22cc936f0b4a6de1acf0abde","amount":"1183575"},{"index":"40713","validator_index":"360720","address":"0x73b2e0e54510239e22cc936f0b4a6de1acf0abde","amount":"1157785"},{"index":"40714","validator_index":"360721","address":"0x73b2e0e54510239e22cc936f0b4a6de1acf0abde","amount":"1143371"},{"index":"40715","validator_index":"360722","address":"0x73b2e0e54510239e22cc936f0b4a6de1acf0abde","amount":"1234787"},{"index":"40716","validator_index":"360723","address":"0x73b2e0e54510239e22cc936f0b4a6de1acf0abde","amount":"1286673"},{"index":"40717","validator_index":"360724","address":"0x73b2e0e54510239e22cc936f0b4a6de1acf0abde","amount":"1419241"},{"index":"40718","validator_index":"360725","address":"0x73b2e0e54510239e22cc936f0b4a6de1acf0abde","amount":"1231015"},{"index":"40719","validator_index":"360726","address":"0x73b2e0e54510239e22cc936f0b4a6de1acf0abde","amount":"1304321"},{"index":"40720","validator_index":"360727","address":"0x73b2e0e54510239e22cc936f0b4a6de1acf0abde","amount":"1236543"}]}}} +``` + + ## Serving the HTTP API over TLS > **Warning**: This feature is currently experimental. diff --git a/book/src/api-lighthouse.md b/book/src/api-lighthouse.md index 7626d640130..a1bc73f646f 100644 --- a/book/src/api-lighthouse.md +++ b/book/src/api-lighthouse.md @@ -547,22 +547,6 @@ reconstruction has yet to be completed. For more information on the specific meanings of these fields see the docs on [Checkpoint Sync](./checkpoint-sync.md#reconstructing-states). -### `/lighthouse/database/reconstruct` - -Instruct Lighthouse to begin reconstructing historic states, see -[Reconstructing States](./checkpoint-sync.md#reconstructing-states). This is an alternative -to the `--reconstruct-historic-states` flag. - -``` -curl -X POST "http://localhost:5052/lighthouse/database/reconstruct" | jq -``` - -```json -"success" -``` - -The endpoint will return immediately. See the beacon node logs for an indication of progress. - ### `/lighthouse/database/historical_blocks` Manually provide `SignedBeaconBlock`s to backfill the database. This is intended diff --git a/book/src/builders.md b/book/src/builders.md index 2be4841ddf3..b0d61124310 100644 --- a/book/src/builders.md +++ b/book/src/builders.md @@ -258,6 +258,9 @@ used in place of one from the builder: INFO Reconstructing a full block using a local payload ``` +## Information for block builders and relays +Block builders and relays can query beacon node events from the [Events API](https://ethereum.github.io/beacon-APIs/#/Events/eventstream). An example of querying the payload attributes in the Events API is outlined in [Beacon node API - Events API](./api-bn.md#events-api) + [mev-rs]: https://github.com/ralexstokes/mev-rs [mev-boost]: https://github.com/flashbots/mev-boost [gas-limit-api]: https://ethereum.github.io/keymanager-APIs/#/Gas%20Limit diff --git a/book/src/validator-inclusion.md b/book/src/validator-inclusion.md index ef81b2b7512..cd31d78d62d 100644 --- a/book/src/validator-inclusion.md +++ b/book/src/validator-inclusion.md @@ -8,7 +8,7 @@ These endpoints are not stable or included in the Ethereum consensus standard AP they are subject to change or removal without a change in major release version. -In order to apply these APIs, you need to have historical states information in the database of your node. This means adding the flag `--reconstruct-historic-states` in the beacon node or using the [/lighthouse/database/reconstruct API](./api-lighthouse.md#lighthousedatabasereconstruct). Once the state reconstruction process is completed, you can apply these APIs to any epoch. +In order to apply these APIs, you need to have historical states information in the database of your node. This means adding the flag `--reconstruct-historic-states` in the beacon node. Once the state reconstruction process is completed, you can apply these APIs to any epoch. ## Endpoints From c3321dddb7e8d8d55a39dadaa6be4abee05dd9a4 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Fri, 6 Oct 2023 06:26:18 +0000 Subject: [PATCH 03/21] Reduce attestation subscription spam from VC (#4806) ## Proposed Changes Instead of sending every attestation subscription every slot to every BN: - Send subscriptions 32, 16, 8, 7, 6, 5, 4, 3 slots before they occur. - Track whether each subscription is sent successfully and retry it in subsequent slots if necessary. ## Additional Info - [x] Add unit tests for `SubscriptionSlots`. - [x] Test on Holesky. - [x] Based on #4774 for testing. --- validator_client/src/duties_service.rs | 192 +++++++++++++++++++++++-- 1 file changed, 177 insertions(+), 15 deletions(-) diff --git a/validator_client/src/duties_service.rs b/validator_client/src/duties_service.rs index a3b3cabcccd..9b9105a6217 100644 --- a/validator_client/src/duties_service.rs +++ b/validator_client/src/duties_service.rs @@ -21,11 +21,12 @@ use eth2::types::{ }; use futures::{stream, StreamExt}; use parking_lot::RwLock; -use safe_arith::ArithError; +use safe_arith::{ArithError, SafeArith}; use slog::{debug, error, info, warn, Logger}; use slot_clock::SlotClock; use std::cmp::min; use std::collections::{hash_map, BTreeMap, HashMap, HashSet}; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; use sync::poll_sync_committee_duties; @@ -33,14 +34,6 @@ use sync::SyncDutiesMap; use tokio::{sync::mpsc::Sender, time::sleep}; use types::{ChainSpec, Epoch, EthSpec, Hash256, PublicKeyBytes, SelectionProof, Slot}; -/// Since the BN does not like it when we subscribe to slots that are close to the current time, we -/// will only subscribe to slots which are further than `SUBSCRIPTION_BUFFER_SLOTS` away. -/// -/// This number is based upon `MIN_PEER_DISCOVERY_SLOT_LOOK_AHEAD` value in the -/// `beacon_node::network::attestation_service` crate. It is not imported directly to avoid -/// bringing in the entire crate. -const SUBSCRIPTION_BUFFER_SLOTS: u64 = 2; - /// Only retain `HISTORICAL_DUTIES_EPOCHS` duties prior to the current epoch. const HISTORICAL_DUTIES_EPOCHS: u64 = 2; @@ -62,6 +55,36 @@ const VALIDATOR_METRICS_MIN_COUNT: usize = 64; /// reduces the amount of data that needs to be transferred. const INITIAL_DUTIES_QUERY_SIZE: usize = 1; +/// Offsets from the attestation duty slot at which a subscription should be sent. +const ATTESTATION_SUBSCRIPTION_OFFSETS: [u64; 8] = [3, 4, 5, 6, 7, 8, 16, 32]; + +/// Check that `ATTESTATION_SUBSCRIPTION_OFFSETS` is sorted ascendingly. +const _: () = assert!({ + let mut i = 0; + loop { + let prev = if i > 0 { + ATTESTATION_SUBSCRIPTION_OFFSETS[i - 1] + } else { + 0 + }; + let curr = ATTESTATION_SUBSCRIPTION_OFFSETS[i]; + if curr < prev { + break false; + } + i += 1; + if i == ATTESTATION_SUBSCRIPTION_OFFSETS.len() { + break true; + } + } +}); +/// Since the BN does not like it when we subscribe to slots that are close to the current time, we +/// will only subscribe to slots which are further than 2 slots away. +/// +/// This number is based upon `MIN_PEER_DISCOVERY_SLOT_LOOK_AHEAD` value in the +/// `beacon_node::network::attestation_service` crate. It is not imported directly to avoid +/// bringing in the entire crate. +const _: () = assert!(ATTESTATION_SUBSCRIPTION_OFFSETS[0] > 2); + #[derive(Debug)] pub enum Error { UnableToReadSlotClock, @@ -84,6 +107,16 @@ pub struct DutyAndProof { pub duty: AttesterData, /// This value is only set to `Some` if the proof indicates that the validator is an aggregator. pub selection_proof: Option, + /// Track which slots we should send subscriptions at for this duty. + /// + /// This value is updated after each subscription is successfully sent. + pub subscription_slots: Arc, +} + +/// Tracker containing the slots at which an attestation subscription should be sent. +pub struct SubscriptionSlots { + /// Pairs of `(slot, already_sent)` in slot-descending order. + slots: Vec<(Slot, AtomicBool)>, } impl DutyAndProof { @@ -111,17 +144,55 @@ impl DutyAndProof { } })?; + let subscription_slots = SubscriptionSlots::new(duty.slot); + Ok(Self { duty, selection_proof, + subscription_slots, }) } /// Create a new `DutyAndProof` with the selection proof waiting to be filled in. pub fn new_without_selection_proof(duty: AttesterData) -> Self { + let subscription_slots = SubscriptionSlots::new(duty.slot); Self { duty, selection_proof: None, + subscription_slots, + } + } +} + +impl SubscriptionSlots { + fn new(duty_slot: Slot) -> Arc { + let slots = ATTESTATION_SUBSCRIPTION_OFFSETS + .into_iter() + .filter_map(|offset| duty_slot.safe_sub(offset).ok()) + .map(|scheduled_slot| (scheduled_slot, AtomicBool::new(false))) + .collect(); + Arc::new(Self { slots }) + } + + /// Return `true` if we should send a subscription at `slot`. + fn should_send_subscription_at(&self, slot: Slot) -> bool { + // Iterate slots from smallest to largest looking for one that hasn't been completed yet. + self.slots + .iter() + .rev() + .any(|(scheduled_slot, already_sent)| { + slot >= *scheduled_slot && !already_sent.load(Ordering::Relaxed) + }) + } + + /// Update our record of subscribed slots to account for successful subscription at `slot`. + fn record_successful_subscription_at(&self, slot: Slot) { + for (scheduled_slot, already_sent) in self.slots.iter().rev() { + if slot >= *scheduled_slot { + already_sent.store(true, Ordering::Relaxed); + } else { + break; + } } } } @@ -574,8 +645,24 @@ async fn poll_beacon_attesters( let subscriptions_timer = metrics::start_timer_vec(&metrics::DUTIES_SERVICE_TIMES, &[metrics::SUBSCRIPTIONS]); - // This vector is likely to be a little oversized, but it won't reallocate. - let mut subscriptions = Vec::with_capacity(local_pubkeys.len() * 2); + // This vector is intentionally oversized by 10% so that it won't reallocate. + // Each validator has 2 attestation duties occuring in the current and next epoch, for which + // they must send `ATTESTATION_SUBSCRIPTION_OFFSETS.len()` subscriptions. These subscription + // slots are approximately evenly distributed over the two epochs, usually with a slight lag + // that balances out (some subscriptions for the current epoch were sent in the previous, and + // some subscriptions for the next next epoch will be sent in the next epoch but aren't included + // in our calculation). We cancel the factor of 2 from the formula for simplicity. + let overallocation_numerator = 110; + let overallocation_denominator = 100; + let num_expected_subscriptions = overallocation_numerator + * std::cmp::max( + 1, + local_pubkeys.len() * ATTESTATION_SUBSCRIPTION_OFFSETS.len() + / E::slots_per_epoch() as usize, + ) + / overallocation_denominator; + let mut subscriptions = Vec::with_capacity(num_expected_subscriptions); + let mut subscription_slots_to_confirm = Vec::with_capacity(num_expected_subscriptions); // For this epoch and the next epoch, produce any beacon committee subscriptions. // @@ -588,10 +675,10 @@ async fn poll_beacon_attesters( .read() .iter() .filter_map(|(_, map)| map.get(epoch)) - // The BN logs a warning if we try and subscribe to current or near-by slots. Give it a - // buffer. .filter(|(_, duty_and_proof)| { - current_slot + SUBSCRIPTION_BUFFER_SLOTS < duty_and_proof.duty.slot + duty_and_proof + .subscription_slots + .should_send_subscription_at(current_slot) }) .for_each(|(_, duty_and_proof)| { let duty = &duty_and_proof.duty; @@ -603,7 +690,8 @@ async fn poll_beacon_attesters( committees_at_slot: duty.committees_at_slot, slot: duty.slot, is_aggregator, - }) + }); + subscription_slots_to_confirm.push(duty_and_proof.subscription_slots.clone()); }); } @@ -632,6 +720,16 @@ async fn poll_beacon_attesters( "Failed to subscribe validators"; "error" => %e ) + } else { + // Record that subscriptions were successfully sent. + debug!( + log, + "Broadcast attestation subscriptions"; + "count" => subscriptions.len(), + ); + for subscription_slots in subscription_slots_to_confirm { + subscription_slots.record_successful_subscription_at(current_slot); + } } } @@ -1200,3 +1298,67 @@ async fn notify_block_production_service( }; } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn subscription_slots_exact() { + for duty_slot in [ + Slot::new(32), + Slot::new(47), + Slot::new(99), + Slot::new(1002003), + ] { + let subscription_slots = SubscriptionSlots::new(duty_slot); + + // Run twice to check idempotence (subscription slots shouldn't be marked as done until + // we mark them manually). + for _ in 0..2 { + for offset in ATTESTATION_SUBSCRIPTION_OFFSETS { + assert!(subscription_slots.should_send_subscription_at(duty_slot - offset)); + } + } + + // Mark each slot as complete and check that all prior slots are still marked + // incomplete. + for (i, offset) in ATTESTATION_SUBSCRIPTION_OFFSETS + .into_iter() + .rev() + .enumerate() + { + subscription_slots.record_successful_subscription_at(duty_slot - offset); + for lower_offset in ATTESTATION_SUBSCRIPTION_OFFSETS + .into_iter() + .rev() + .skip(i + 1) + { + assert!(lower_offset < offset); + assert!( + subscription_slots.should_send_subscription_at(duty_slot - lower_offset) + ); + } + } + } + } + #[test] + fn subscription_slots_mark_multiple() { + for (i, offset) in ATTESTATION_SUBSCRIPTION_OFFSETS.into_iter().enumerate() { + let duty_slot = Slot::new(64); + let subscription_slots = SubscriptionSlots::new(duty_slot); + + subscription_slots.record_successful_subscription_at(duty_slot - offset); + + // All past offsets (earlier slots) should be marked as complete. + for (j, other_offset) in ATTESTATION_SUBSCRIPTION_OFFSETS.into_iter().enumerate() { + let past = j >= i; + assert_eq!(other_offset >= offset, past); + assert_eq!( + subscription_slots.should_send_subscription_at(duty_slot - other_offset), + !past + ); + } + } + } +} From 8660043024f95a31db9b0027a2e9eacc28d7e727 Mon Sep 17 00:00:00 2001 From: ethDreamer <37123614+ethDreamer@users.noreply.github.com> Date: Tue, 10 Oct 2023 23:51:00 -0500 Subject: [PATCH 04/21] Prevent Overflow LRU Cache from Exploding (#4801) * Initial Commit of State LRU Cache * Build State Caches After Reconstruction * Cleanup Duplicated Code in OverflowLRUCache Tests * Added Test for State LRU Cache * Prune Cache of Old States During Maintenance * Address Michael's Comments * Few More Comments * Removed Unused impl * Last touch up * Fix Clippy --- .../src/block_verification_types.rs | 15 +- .../src/data_availability_checker.rs | 50 +-- .../availability_view.rs | 12 +- .../src/data_availability_checker/error.rs | 79 ++++ .../overflow_lru_cache.rs | 382 +++++++++++------- .../state_lru_cache.rs | 230 +++++++++++ .../gossip_methods.rs | 17 +- .../network/src/sync/block_lookups/mod.rs | 54 +-- consensus/types/src/beacon_state.rs | 77 ---- 9 files changed, 577 insertions(+), 339 deletions(-) create mode 100644 beacon_node/beacon_chain/src/data_availability_checker/error.rs create mode 100644 beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs diff --git a/beacon_node/beacon_chain/src/block_verification_types.rs b/beacon_node/beacon_chain/src/block_verification_types.rs index 7dae9d6cbe7..3dfd45b007a 100644 --- a/beacon_node/beacon_chain/src/block_verification_types.rs +++ b/beacon_node/beacon_chain/src/block_verification_types.rs @@ -5,15 +5,10 @@ pub use crate::data_availability_checker::{AvailableBlock, MaybeAvailableBlock}; use crate::eth1_finalization_cache::Eth1FinalizationData; use crate::{get_block_root, GossipVerifiedBlock, PayloadVerificationOutcome}; use derivative::Derivative; -use ssz_derive::{Decode, Encode}; use ssz_types::VariableList; use state_processing::ConsensusContext; use std::sync::Arc; -use types::blob_sidecar::FixedBlobSidecarList; -use types::{ - blob_sidecar::BlobIdentifier, ssz_tagged_beacon_state, ssz_tagged_signed_beacon_block, - ssz_tagged_signed_beacon_block_arc, -}; +use types::blob_sidecar::{BlobIdentifier, FixedBlobSidecarList}; use types::{ BeaconBlockRef, BeaconState, BlindedPayload, BlobSidecarList, Epoch, EthSpec, Hash256, SignedBeaconBlock, SignedBeaconBlockHeader, Slot, @@ -251,9 +246,7 @@ impl AvailableExecutedBlock { /// A block that has completed all pre-deneb block processing checks, verification /// by an EL client but does not have all requisite blob data to get imported into /// fork choice. -#[derive(Encode, Decode, Clone)] pub struct AvailabilityPendingExecutedBlock { - #[ssz(with = "ssz_tagged_signed_beacon_block_arc")] pub block: Arc>, pub import_data: BlockImportData, pub payload_verification_outcome: PayloadVerificationOutcome, @@ -285,14 +278,10 @@ impl AvailabilityPendingExecutedBlock { } } -#[derive(Debug, PartialEq, Encode, Decode, Clone)] -// TODO (mark): investigate using an Arc / Arc -// here to make this cheaper to clone +#[derive(Debug, PartialEq)] pub struct BlockImportData { pub block_root: Hash256, - #[ssz(with = "ssz_tagged_beacon_state")] pub state: BeaconState, - #[ssz(with = "ssz_tagged_signed_beacon_block")] pub parent_block: SignedBeaconBlock>, pub parent_eth1_finalization_data: Eth1FinalizationData, pub confirmed_state_roots: Vec, diff --git a/beacon_node/beacon_chain/src/data_availability_checker.rs b/beacon_node/beacon_chain/src/data_availability_checker.rs index be427ae9f69..e1024da46c9 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker.rs @@ -10,17 +10,14 @@ use crate::data_availability_checker::overflow_lru_cache::OverflowLRUCache; use crate::data_availability_checker::processing_cache::ProcessingCache; use crate::{BeaconChain, BeaconChainTypes, BeaconStore}; use kzg::Kzg; -use kzg::{Error as KzgError, KzgCommitment}; use parking_lot::RwLock; pub use processing_cache::ProcessingComponents; use slasher::test_utils::E; use slog::{debug, error, Logger}; use slot_clock::SlotClock; -use ssz_types::Error; use std::fmt; use std::fmt::Debug; use std::sync::Arc; -use strum::IntoStaticStr; use task_executor::TaskExecutor; use types::beacon_block_body::{KzgCommitmentOpts, KzgCommitments}; use types::blob_sidecar::{BlobIdentifier, BlobSidecar, FixedBlobSidecarList}; @@ -29,8 +26,12 @@ use types::{BlobSidecarList, ChainSpec, Epoch, EthSpec, Hash256, SignedBeaconBlo mod availability_view; mod child_components; +mod error; mod overflow_lru_cache; mod processing_cache; +mod state_lru_cache; + +pub use error::{Error as AvailabilityCheckError, ErrorCategory as AvailabilityCheckErrorCategory}; /// The LRU Cache stores `PendingComponents` which can store up to /// `MAX_BLOBS_PER_BLOCK = 6` blobs each. A `BlobSidecar` is 0.131256 MB. So @@ -38,45 +39,8 @@ mod processing_cache; /// to 1024 means the maximum size of the cache is ~ 0.8 GB. But the cache /// will target a size of less than 75% of capacity. pub const OVERFLOW_LRU_CAPACITY: usize = 1024; - -#[derive(Debug, IntoStaticStr)] -pub enum AvailabilityCheckError { - Kzg(KzgError), - KzgNotInitialized, - KzgVerificationFailed, - KzgCommitmentMismatch { - blob_commitment: KzgCommitment, - block_commitment: KzgCommitment, - }, - Unexpected, - SszTypes(ssz_types::Error), - MissingBlobs, - BlobIndexInvalid(u64), - StoreError(store::Error), - DecodeError(ssz::DecodeError), - InconsistentBlobBlockRoots { - block_root: Hash256, - blob_block_root: Hash256, - }, -} - -impl From for AvailabilityCheckError { - fn from(value: Error) -> Self { - Self::SszTypes(value) - } -} - -impl From for AvailabilityCheckError { - fn from(value: store::Error) -> Self { - Self::StoreError(value) - } -} - -impl From for AvailabilityCheckError { - fn from(value: ssz::DecodeError) -> Self { - Self::DecodeError(value) - } -} +/// Until tree-states is implemented, we can't store very many states in memory :( +pub const STATE_LRU_CAPACITY: usize = 2; /// This includes a cache for any blocks or blobs that have been received over gossip or RPC /// and are awaiting more components before they can be imported. Additionally the @@ -120,7 +84,7 @@ impl DataAvailabilityChecker { log: &Logger, spec: ChainSpec, ) -> Result { - let overflow_cache = OverflowLRUCache::new(OVERFLOW_LRU_CAPACITY, store)?; + let overflow_cache = OverflowLRUCache::new(OVERFLOW_LRU_CAPACITY, store, spec.clone())?; Ok(Self { processing_cache: <_>::default(), availability_cache: Arc::new(overflow_cache), diff --git a/beacon_node/beacon_chain/src/data_availability_checker/availability_view.rs b/beacon_node/beacon_chain/src/data_availability_checker/availability_view.rs index eb1f23d48fb..fea2ee101ee 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker/availability_view.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker/availability_view.rs @@ -1,9 +1,9 @@ use super::child_components::ChildComponents; +use super::state_lru_cache::DietAvailabilityPendingExecutedBlock; use crate::blob_verification::KzgVerifiedBlob; use crate::block_verification_types::AsBlock; use crate::data_availability_checker::overflow_lru_cache::PendingComponents; use crate::data_availability_checker::ProcessingComponents; -use crate::AvailabilityPendingExecutedBlock; use kzg::KzgCommitment; use ssz_types::FixedVector; use std::sync::Arc; @@ -190,7 +190,7 @@ impl_availability_view!( impl_availability_view!( PendingComponents, - AvailabilityPendingExecutedBlock, + DietAvailabilityPendingExecutedBlock, KzgVerifiedBlob, executed_block, verified_blobs @@ -225,7 +225,7 @@ impl GetCommitment for KzgCommitment { } // These implementations are required to implement `AvailabilityView` for `PendingComponents`. -impl GetCommitments for AvailabilityPendingExecutedBlock { +impl GetCommitments for DietAvailabilityPendingExecutedBlock { fn get_commitments(&self) -> KzgCommitments { self.as_block() .message() @@ -235,6 +235,7 @@ impl GetCommitments for AvailabilityPendingExecutedBlock { .unwrap_or_default() } } + impl GetCommitment for KzgVerifiedBlob { fn get_commitment(&self) -> &KzgCommitment { &self.as_blob().kzg_commitment @@ -264,6 +265,7 @@ pub mod tests { use crate::block_verification_types::BlockImportData; use crate::eth1_finalization_cache::Eth1FinalizationData; use crate::test_utils::{generate_rand_block_and_blobs, NumBlobs}; + use crate::AvailabilityPendingExecutedBlock; use crate::PayloadVerificationOutcome; use eth2_network_config::get_trusted_setup; use fork_choice::PayloadVerificationStatus; @@ -346,7 +348,7 @@ pub mod tests { } type PendingComponentsSetup = ( - AvailabilityPendingExecutedBlock, + DietAvailabilityPendingExecutedBlock, FixedVector>, ::MaxBlobsPerBlock>, FixedVector>, ::MaxBlobsPerBlock>, ); @@ -395,7 +397,7 @@ pub mod tests { is_valid_merge_transition_block: false, }, }; - (block, blobs, invalid_blobs) + (block.into(), blobs, invalid_blobs) } type ChildComponentsSetup = ( diff --git a/beacon_node/beacon_chain/src/data_availability_checker/error.rs b/beacon_node/beacon_chain/src/data_availability_checker/error.rs new file mode 100644 index 00000000000..5415d1f9588 --- /dev/null +++ b/beacon_node/beacon_chain/src/data_availability_checker/error.rs @@ -0,0 +1,79 @@ +use kzg::{Error as KzgError, KzgCommitment}; +use strum::IntoStaticStr; +use types::{BeaconStateError, Hash256}; + +#[derive(Debug, IntoStaticStr)] +pub enum Error { + Kzg(KzgError), + KzgNotInitialized, + KzgVerificationFailed, + KzgCommitmentMismatch { + blob_commitment: KzgCommitment, + block_commitment: KzgCommitment, + }, + Unexpected, + SszTypes(ssz_types::Error), + MissingBlobs, + BlobIndexInvalid(u64), + StoreError(store::Error), + DecodeError(ssz::DecodeError), + InconsistentBlobBlockRoots { + block_root: Hash256, + blob_block_root: Hash256, + }, + ParentStateMissing(Hash256), + BlockReplayError(state_processing::BlockReplayError), + RebuildingStateCaches(BeaconStateError), +} + +pub enum ErrorCategory { + /// Internal Errors (not caused by peers) + Internal, + /// Errors caused by faulty / malicious peers + Malicious, +} + +impl Error { + pub fn category(&self) -> ErrorCategory { + match self { + Error::KzgNotInitialized + | Error::SszTypes(_) + | Error::MissingBlobs + | Error::StoreError(_) + | Error::DecodeError(_) + | Error::Unexpected + | Error::ParentStateMissing(_) + | Error::BlockReplayError(_) + | Error::RebuildingStateCaches(_) => ErrorCategory::Internal, + Error::Kzg(_) + | Error::BlobIndexInvalid(_) + | Error::KzgCommitmentMismatch { .. } + | Error::KzgVerificationFailed + | Error::InconsistentBlobBlockRoots { .. } => ErrorCategory::Malicious, + } + } +} + +impl From for Error { + fn from(value: ssz_types::Error) -> Self { + Self::SszTypes(value) + } +} + +impl From for Error { + fn from(value: store::Error) -> Self { + Self::StoreError(value) + } +} + +impl From for Error { + fn from(value: ssz::DecodeError) -> Self { + Self::DecodeError(value) + } +} + +impl From for Error { + fn from(value: state_processing::BlockReplayError) -> Self { + Self::BlockReplayError(value) + } +} diff --git a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs index 8bf16c17323..e4f1685d5af 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs @@ -27,10 +27,11 @@ //! On startup, the keys of these components are stored in memory and will be loaded in //! the cache when they are accessed. +use super::state_lru_cache::{DietAvailabilityPendingExecutedBlock, StateLRUCache}; use crate::beacon_chain::BeaconStore; use crate::blob_verification::KzgVerifiedBlob; use crate::block_verification_types::{ - AsBlock, AvailabilityPendingExecutedBlock, AvailableBlock, AvailableExecutedBlock, + AvailabilityPendingExecutedBlock, AvailableBlock, AvailableExecutedBlock, }; use crate::data_availability_checker::availability_view::AvailabilityView; use crate::data_availability_checker::{Availability, AvailabilityCheckError}; @@ -43,7 +44,7 @@ use ssz_derive::{Decode, Encode}; use ssz_types::{FixedVector, VariableList}; use std::{collections::HashSet, sync::Arc}; use types::blob_sidecar::BlobIdentifier; -use types::{BlobSidecar, Epoch, EthSpec, Hash256}; +use types::{BlobSidecar, ChainSpec, Epoch, EthSpec, Hash256}; /// This represents the components of a partially available block /// @@ -53,7 +54,7 @@ use types::{BlobSidecar, Epoch, EthSpec, Hash256}; pub struct PendingComponents { pub block_root: Hash256, pub verified_blobs: FixedVector>, T::MaxBlobsPerBlock>, - pub executed_block: Option>, + pub executed_block: Option>, } impl PendingComponents { @@ -68,17 +69,25 @@ impl PendingComponents { /// Verifies an `SignedBeaconBlock` against a set of KZG verified blobs. /// This does not check whether a block *should* have blobs, these checks should have been /// completed when producing the `AvailabilityPendingBlock`. - pub fn make_available(self) -> Result, AvailabilityCheckError> { + /// + /// WARNING: This function can potentially take a lot of time if the state needs to be + /// reconstructed from disk. Ensure you are not holding any write locks while calling this. + pub fn make_available(self, recover: R) -> Result, AvailabilityCheckError> + where + R: FnOnce( + DietAvailabilityPendingExecutedBlock, + ) -> Result, AvailabilityCheckError>, + { let Self { block_root, verified_blobs, executed_block, } = self; - let Some(executed_block) = executed_block else { + let Some(diet_executed_block) = executed_block else { return Err(AvailabilityCheckError::Unexpected); }; - let num_blobs_expected = executed_block.num_blobs_expected(); + let num_blobs_expected = diet_executed_block.num_blobs_expected(); let Some(verified_blobs) = verified_blobs .into_iter() .cloned() @@ -90,6 +99,8 @@ impl PendingComponents { }; let verified_blobs = VariableList::new(verified_blobs)?; + let executed_block = recover(diet_executed_block)?; + let AvailabilityPendingExecutedBlock { block, import_data, @@ -109,7 +120,7 @@ impl PendingComponents { pub fn epoch(&self) -> Option { self.executed_block .as_ref() - .map(|pending_block| pending_block.block.epoch()) + .map(|pending_block| pending_block.as_block().epoch()) .or_else(|| { for maybe_blob in self.verified_blobs.iter() { if maybe_blob.is_some() { @@ -208,9 +219,10 @@ impl OverflowStore { OverflowKey::Block(_) => { maybe_pending_components .get_or_insert_with(|| PendingComponents::empty(block_root)) - .executed_block = Some(AvailabilityPendingExecutedBlock::from_ssz_bytes( - value_bytes.as_slice(), - )?); + .executed_block = + Some(DietAvailabilityPendingExecutedBlock::from_ssz_bytes( + value_bytes.as_slice(), + )?); } OverflowKey::Blob(_, index) => { *maybe_pending_components @@ -356,6 +368,9 @@ pub struct OverflowLRUCache { critical: RwLock>, /// This is how we read and write components to the disk overflow_store: OverflowStore, + /// This cache holds a limited number of states in memory and reconstructs them + /// from disk when necessary. This is necessary until we merge tree-states + state_cache: StateLRUCache, /// Mutex to guard maintenance methods which move data between disk and memory maintenance_lock: Mutex<()>, /// The capacity of the LRU cache @@ -366,13 +381,15 @@ impl OverflowLRUCache { pub fn new( capacity: usize, beacon_store: BeaconStore, + spec: ChainSpec, ) -> Result { - let overflow_store = OverflowStore(beacon_store); + let overflow_store = OverflowStore(beacon_store.clone()); let mut critical = Critical::new(capacity); critical.reload_store_keys(&overflow_store)?; Ok(Self { critical: RwLock::new(critical), overflow_store, + state_cache: StateLRUCache::new(beacon_store, spec), maintenance_lock: Mutex::new(()), capacity, }) @@ -426,7 +443,11 @@ impl OverflowLRUCache { pending_components.merge_blobs(fixed_blobs); if pending_components.is_available() { - pending_components.make_available() + // No need to hold the write lock anymore + drop(write_lock); + pending_components.make_available(|diet_block| { + self.state_cache.recover_pending_executed_block(diet_block) + }) } else { write_lock.put_pending_components( block_root, @@ -446,17 +467,26 @@ impl OverflowLRUCache { let mut write_lock = self.critical.write(); let block_root = executed_block.import_data.block_root; + // register the block to get the diet block + let diet_executed_block = self + .state_cache + .register_pending_executed_block(executed_block); + // Grab existing entry or create a new entry. let mut pending_components = write_lock .pop_pending_components(block_root, &self.overflow_store)? .unwrap_or_else(|| PendingComponents::empty(block_root)); // Merge in the block. - pending_components.merge_block(executed_block); + pending_components.merge_block(diet_executed_block); // Check if we have all components and entire set is consistent. if pending_components.is_available() { - pending_components.make_available() + // No need to hold the write lock anymore + drop(write_lock); + pending_components.make_available(|diet_block| { + self.state_cache.recover_pending_executed_block(diet_block) + }) } else { write_lock.put_pending_components( block_root, @@ -493,6 +523,8 @@ impl OverflowLRUCache { self.maintain_threshold(threshold, cutoff_epoch)?; // clean up any keys on the disk that shouldn't be there self.prune_disk(cutoff_epoch)?; + // clean up any lingering states in the state cache + self.state_cache.do_maintenance(cutoff_epoch); Ok(()) } @@ -612,10 +644,10 @@ impl OverflowLRUCache { delete_if_outdated(self, current_block_data)?; let current_epoch = match &overflow_key { OverflowKey::Block(_) => { - AvailabilityPendingExecutedBlock::::from_ssz_bytes( + DietAvailabilityPendingExecutedBlock::::from_ssz_bytes( value_bytes.as_slice(), )? - .block + .as_block() .epoch() } OverflowKey::Blob(_, _) => { @@ -639,6 +671,12 @@ impl OverflowLRUCache { drop(maintenance_lock); Ok(()) } + + #[cfg(test)] + /// get the state cache for inspection (used only for tests) + pub fn state_lru_cache(&self) -> &StateLRUCache { + &self.state_cache + } } impl ssz::Encode for OverflowKey { @@ -711,11 +749,11 @@ mod test { validate_blob_sidecar_for_gossip, verify_kzg_for_blob, GossipVerifiedBlob, }, block_verification::PayloadVerificationOutcome, - block_verification_types::BlockImportData, + block_verification_types::{AsBlock, BlockImportData}, + data_availability_checker::STATE_LRU_CAPACITY, eth1_finalization_cache::Eth1FinalizationData, test_utils::{BaseHarnessType, BeaconChainHarness, DiskHarnessType}, }; - use execution_layer::test_utils::DEFAULT_TERMINAL_BLOCK; use fork_choice::PayloadVerificationStatus; use logging::test_logger; use slog::{info, Logger}; @@ -724,7 +762,6 @@ mod test { use std::ops::AddAssign; use store::{HotColdDB, ItemStore, LevelDB, StoreConfig}; use tempfile::{tempdir, TempDir}; - use types::beacon_state::ssz_tagged_beacon_state; use types::{ChainSpec, ExecPayload, MinimalEthSpec}; const LOW_VALIDATOR_COUNT: usize = 32; @@ -754,7 +791,7 @@ mod test { async fn get_deneb_chain( log: Logger, db_path: &TempDir, - ) -> BeaconChainHarness, LevelDB>> { + ) -> BeaconChainHarness> { let altair_fork_epoch = Epoch::new(1); let bellatrix_fork_epoch = Epoch::new(2); let bellatrix_fork_slot = bellatrix_fork_epoch.start_slot(E::slots_per_epoch()); @@ -837,91 +874,8 @@ mod test { } } - #[tokio::test] - async fn ssz_tagged_beacon_state_encode_decode_equality() { - type E = MinimalEthSpec; - let altair_fork_epoch = Epoch::new(1); - let altair_fork_slot = altair_fork_epoch.start_slot(E::slots_per_epoch()); - let bellatrix_fork_epoch = Epoch::new(2); - let merge_fork_slot = bellatrix_fork_epoch.start_slot(E::slots_per_epoch()); - let capella_fork_epoch = Epoch::new(3); - let capella_fork_slot = capella_fork_epoch.start_slot(E::slots_per_epoch()); - let deneb_fork_epoch = Epoch::new(4); - let deneb_fork_slot = deneb_fork_epoch.start_slot(E::slots_per_epoch()); - - let mut spec = E::default_spec(); - spec.altair_fork_epoch = Some(altair_fork_epoch); - spec.bellatrix_fork_epoch = Some(bellatrix_fork_epoch); - spec.capella_fork_epoch = Some(capella_fork_epoch); - spec.deneb_fork_epoch = Some(deneb_fork_epoch); - let genesis_block = execution_layer::test_utils::generate_genesis_block( - spec.terminal_total_difficulty, - DEFAULT_TERMINAL_BLOCK, - ) - .unwrap(); - spec.terminal_block_hash = genesis_block.block_hash; - spec.terminal_block_hash_activation_epoch = bellatrix_fork_epoch; - - let harness = BeaconChainHarness::builder(E::default()) - .spec(spec) - .logger(logging::test_logger()) - .deterministic_keypairs(LOW_VALIDATOR_COUNT) - .fresh_ephemeral_store() - .mock_execution_layer() - .build(); - - let mut state = harness.get_current_state(); - assert!(state.as_base().is_ok()); - let encoded = ssz_tagged_beacon_state::encode::as_ssz_bytes(&state); - let decoded = - ssz_tagged_beacon_state::decode::from_ssz_bytes(&encoded).expect("should decode"); - state.drop_all_caches().expect("should drop caches"); - assert_eq!(state, decoded, "Encoded and decoded states should be equal"); - - harness.extend_to_slot(altair_fork_slot).await; - - let mut state = harness.get_current_state(); - assert!(state.as_altair().is_ok()); - let encoded = ssz_tagged_beacon_state::encode::as_ssz_bytes(&state); - let decoded = - ssz_tagged_beacon_state::decode::from_ssz_bytes(&encoded).expect("should decode"); - state.drop_all_caches().expect("should drop caches"); - assert_eq!(state, decoded, "Encoded and decoded states should be equal"); - - harness.extend_to_slot(merge_fork_slot).await; - - let mut state = harness.get_current_state(); - assert!(state.as_merge().is_ok()); - let encoded = ssz_tagged_beacon_state::encode::as_ssz_bytes(&state); - let decoded = - ssz_tagged_beacon_state::decode::from_ssz_bytes(&encoded).expect("should decode"); - state.drop_all_caches().expect("should drop caches"); - assert_eq!(state, decoded, "Encoded and decoded states should be equal"); - - harness.extend_to_slot(capella_fork_slot).await; - - let mut state = harness.get_current_state(); - assert!(state.as_capella().is_ok()); - let encoded = ssz_tagged_beacon_state::encode::as_ssz_bytes(&state); - let decoded = - ssz_tagged_beacon_state::decode::from_ssz_bytes(&encoded).expect("should decode"); - state.drop_all_caches().expect("should drop caches"); - assert_eq!(state, decoded, "Encoded and decoded states should be equal"); - - harness.extend_to_slot(deneb_fork_slot).await; - - let mut state = harness.get_current_state(); - assert!(state.as_deneb().is_ok()); - let encoded = ssz_tagged_beacon_state::encode::as_ssz_bytes(&state); - let decoded = - ssz_tagged_beacon_state::decode::from_ssz_bytes(&encoded).expect("should decode"); - state.drop_all_caches().expect("should drop caches"); - assert_eq!(state, decoded, "Encoded and decoded states should be equal"); - } - async fn availability_pending_block( harness: &BeaconChainHarness>, - log: Logger, ) -> ( AvailabilityPendingExecutedBlock, Vec>>, @@ -932,6 +886,7 @@ mod test { Cold: ItemStore, { let chain = &harness.chain; + let log = chain.log.clone(); let head = chain.head_snapshot(); let parent_state = head.beacon_state.clone_with_only_committee_caches(); @@ -1010,22 +965,36 @@ mod test { (availability_pending_block, gossip_verified_blobs) } - #[tokio::test] - async fn overflow_cache_test_insert_components() { - type E = MinimalEthSpec; - type T = DiskHarnessType; + async fn setup_harness_and_cache( + capacity: usize, + ) -> ( + BeaconChainHarness>, + Arc>, + ) + where + E: EthSpec, + T: BeaconChainTypes, ColdStore = LevelDB, EthSpec = E>, + { let log = test_logger(); let chain_db_path = tempdir().expect("should get temp dir"); - let harness: BeaconChainHarness = get_deneb_chain(log.clone(), &chain_db_path).await; + let harness = get_deneb_chain(log.clone(), &chain_db_path).await; let spec = harness.spec.clone(); - let capacity = 4; - let db_path = tempdir().expect("should get temp dir"); - let test_store = get_store_with_spec::(&db_path, spec.clone(), log.clone()); + let test_store = harness.chain.store.clone(); let cache = Arc::new( - OverflowLRUCache::::new(capacity, test_store).expect("should create cache"), + OverflowLRUCache::::new(capacity, test_store, spec.clone()) + .expect("should create cache"), ); + (harness, cache) + } + + #[tokio::test] + async fn overflow_cache_test_insert_components() { + type E = MinimalEthSpec; + type T = DiskHarnessType; + let capacity = 4; + let (harness, cache) = setup_harness_and_cache::(capacity).await; - let (pending_block, blobs) = availability_pending_block(&harness, log.clone()).await; + let (pending_block, blobs) = availability_pending_block(&harness).await; let root = pending_block.import_data.block_root; let blobs_expected = pending_block.num_blobs_expected(); @@ -1093,7 +1062,7 @@ mod test { "cache should be empty now that all components available" ); - let (pending_block, blobs) = availability_pending_block(&harness, log.clone()).await; + let (pending_block, blobs) = availability_pending_block(&harness).await; let blobs_expected = pending_block.num_blobs_expected(); assert_eq!( blobs.len(), @@ -1134,22 +1103,14 @@ mod test { async fn overflow_cache_test_overflow() { type E = MinimalEthSpec; type T = DiskHarnessType; - let log = test_logger(); - let chain_db_path = tempdir().expect("should get temp dir"); - let harness: BeaconChainHarness = get_deneb_chain(log.clone(), &chain_db_path).await; - let spec = harness.spec.clone(); let capacity = 4; - let db_path = tempdir().expect("should get temp dir"); - let test_store = get_store_with_spec::(&db_path, spec.clone(), log.clone()); - let cache = Arc::new( - OverflowLRUCache::::new(capacity, test_store).expect("should create cache"), - ); + let (harness, cache) = setup_harness_and_cache::(capacity).await; let mut pending_blocks = VecDeque::new(); let mut pending_blobs = VecDeque::new(); let mut roots = VecDeque::new(); while pending_blobs.len() < capacity + 1 { - let (pending_block, blobs) = availability_pending_block(&harness, log.clone()).await; + let (pending_block, blobs) = availability_pending_block(&harness).await; if pending_block.num_blobs_expected() == 0 { // we need blocks with blobs continue; @@ -1293,29 +1254,19 @@ mod test { async fn overflow_cache_test_maintenance() { type E = MinimalEthSpec; type T = DiskHarnessType; - let log = test_logger(); - let chain_db_path = tempdir().expect("should get temp dir"); - let harness: BeaconChainHarness = get_deneb_chain(log.clone(), &chain_db_path).await; - let spec = harness.spec.clone(); - let n_epochs = 4; let capacity = E::slots_per_epoch() as usize; - let db_path = tempdir().expect("should get temp dir"); - let test_store = get_store_with_spec::(&db_path, spec.clone(), log.clone()); - let cache = Arc::new( - OverflowLRUCache::::new(capacity, test_store).expect("should create cache"), - ); + let (harness, cache) = setup_harness_and_cache::(capacity).await; + let n_epochs = 4; let mut pending_blocks = VecDeque::new(); let mut pending_blobs = VecDeque::new(); - let mut roots = VecDeque::new(); let mut epoch_count = BTreeMap::new(); while pending_blobs.len() < n_epochs * capacity { - let (pending_block, blobs) = availability_pending_block(&harness, log.clone()).await; + let (pending_block, blobs) = availability_pending_block(&harness).await; if pending_block.num_blobs_expected() == 0 { // we need blocks with blobs continue; } - let root = pending_block.block.canonical_root(); let epoch = pending_block .block .as_block() @@ -1325,7 +1276,6 @@ mod test { pending_blocks.push_back(pending_block); pending_blobs.push_back(blobs); - roots.push_back(root); } let kzg = harness @@ -1424,7 +1374,7 @@ mod test { let mem_keys = cache.critical.read().in_memory.len(); expected_length -= count; info!( - log, + harness.chain.log, "EPOCH: {} DISK KEYS: {} MEM KEYS: {} TOTAL: {} EXPECTED: {}", epoch, disk_keys, @@ -1444,29 +1394,19 @@ mod test { async fn overflow_cache_test_persist_recover() { type E = MinimalEthSpec; type T = DiskHarnessType; - let log = test_logger(); - let chain_db_path = tempdir().expect("should get temp dir"); - let harness: BeaconChainHarness = get_deneb_chain(log.clone(), &chain_db_path).await; - let spec = harness.spec.clone(); - let n_epochs = 4; let capacity = E::slots_per_epoch() as usize; - let db_path = tempdir().expect("should get temp dir"); - let test_store = get_store_with_spec::(&db_path, spec.clone(), log.clone()); - let cache = Arc::new( - OverflowLRUCache::::new(capacity, test_store.clone()).expect("should create cache"), - ); + let (harness, cache) = setup_harness_and_cache::(capacity).await; + let n_epochs = 4; let mut pending_blocks = VecDeque::new(); let mut pending_blobs = VecDeque::new(); - let mut roots = VecDeque::new(); let mut epoch_count = BTreeMap::new(); while pending_blobs.len() < n_epochs * capacity { - let (pending_block, blobs) = availability_pending_block(&harness, log.clone()).await; + let (pending_block, blobs) = availability_pending_block(&harness).await; if pending_block.num_blobs_expected() == 0 { // we need blocks with blobs continue; } - let root = pending_block.block.as_block().canonical_root(); let epoch = pending_block .block .as_block() @@ -1476,7 +1416,6 @@ mod test { pending_blocks.push_back(pending_block); pending_blobs.push_back(blobs); - roots.push_back(root); } let kzg = harness @@ -1580,8 +1519,12 @@ mod test { drop(cache); // create a new cache with the same store - let recovered_cache = - OverflowLRUCache::::new(capacity, test_store).expect("should recover cache"); + let recovered_cache = OverflowLRUCache::::new( + capacity, + harness.chain.store.clone(), + harness.chain.spec.clone(), + ) + .expect("should recover cache"); // again, everything should be on disk assert_eq!( recovered_cache @@ -1622,4 +1565,133 @@ mod test { } } } + + #[tokio::test] + // ensure the state cache keeps memory usage low and that it can properly recover states + // THIS TEST CAN BE DELETED ONCE TREE STATES IS MERGED AND WE RIP OUT THE STATE CACHE + async fn overflow_cache_test_state_cache() { + type E = MinimalEthSpec; + type T = DiskHarnessType; + let capacity = STATE_LRU_CAPACITY * 2; + let (harness, cache) = setup_harness_and_cache::(capacity).await; + + let mut pending_blocks = VecDeque::new(); + let mut states = Vec::new(); + let mut state_roots = Vec::new(); + // Get enough blocks to fill the cache to capacity, ensuring all blocks have blobs + while pending_blocks.len() < capacity { + let (pending_block, _) = availability_pending_block(&harness).await; + if pending_block.num_blobs_expected() == 0 { + // we need blocks with blobs + continue; + } + let state_root = pending_block.import_data.state.canonical_root(); + states.push(pending_block.import_data.state.clone()); + pending_blocks.push_back(pending_block); + state_roots.push(state_root); + } + + let state_cache = cache.state_lru_cache().lru_cache(); + let mut pushed_diet_blocks = VecDeque::new(); + + for i in 0..capacity { + let pending_block = pending_blocks.pop_front().expect("should have block"); + let block_root = pending_block.as_block().canonical_root(); + + assert_eq!( + state_cache.read().len(), + std::cmp::min(i, STATE_LRU_CAPACITY), + "state cache should be empty at start" + ); + + if i >= STATE_LRU_CAPACITY { + let lru_root = state_roots[i - STATE_LRU_CAPACITY]; + assert_eq!( + state_cache.read().peek_lru().map(|(root, _)| root), + Some(&lru_root), + "lru block should be in cache" + ); + } + + // put the block in the cache + let availability = cache + .put_pending_executed_block(pending_block) + .expect("should put block"); + + // grab the diet block from the cache for later testing + let diet_block = cache + .critical + .read() + .in_memory + .peek(&block_root) + .map(|pending_components| { + pending_components + .executed_block + .clone() + .expect("should exist") + }) + .expect("should exist"); + pushed_diet_blocks.push_back(diet_block); + + // should be unavailable since we made sure all blocks had blobs + assert!( + matches!(availability, Availability::MissingComponents(_)), + "should be pending blobs" + ); + + if i >= STATE_LRU_CAPACITY { + let evicted_index = i - STATE_LRU_CAPACITY; + let evicted_root = state_roots[evicted_index]; + assert!( + state_cache.read().peek(&evicted_root).is_none(), + "lru root should be evicted" + ); + // get the diet block via direct conversion (testing only) + let diet_block = pushed_diet_blocks.pop_front().expect("should have block"); + // reconstruct the pending block by replaying the block on the parent state + let recovered_pending_block = cache + .state_lru_cache() + .reconstruct_pending_executed_block(diet_block) + .expect("should reconstruct pending block"); + + // assert the recovered state is the same as the original + assert_eq!( + recovered_pending_block.import_data.state, states[evicted_index], + "recovered state should be the same as the original" + ); + } + } + + // now check the last block + let last_block = pushed_diet_blocks.pop_back().expect("should exist").clone(); + // the state should still be in the cache + assert!( + state_cache + .read() + .peek(&last_block.as_block().state_root()) + .is_some(), + "last block state should still be in cache" + ); + // get the diet block via direct conversion (testing only) + let diet_block = last_block.clone(); + // recover the pending block from the cache + let recovered_pending_block = cache + .state_lru_cache() + .recover_pending_executed_block(diet_block) + .expect("should reconstruct pending block"); + // assert the recovered state is the same as the original + assert_eq!( + Some(&recovered_pending_block.import_data.state), + states.last(), + "recovered state should be the same as the original" + ); + // the state should no longer be in the cache + assert!( + state_cache + .read() + .peek(&last_block.as_block().state_root()) + .is_none(), + "last block state should no longer be in cache" + ); + } } diff --git a/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs b/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs new file mode 100644 index 00000000000..d3348b67fb6 --- /dev/null +++ b/beacon_node/beacon_chain/src/data_availability_checker/state_lru_cache.rs @@ -0,0 +1,230 @@ +use crate::block_verification_types::AsBlock; +use crate::{ + block_verification_types::BlockImportData, + data_availability_checker::{AvailabilityCheckError, STATE_LRU_CAPACITY}, + eth1_finalization_cache::Eth1FinalizationData, + AvailabilityPendingExecutedBlock, BeaconChainTypes, BeaconStore, PayloadVerificationOutcome, +}; +use lru::LruCache; +use parking_lot::RwLock; +use ssz_derive::{Decode, Encode}; +use state_processing::{BlockReplayer, ConsensusContext, StateProcessingStrategy}; +use std::sync::Arc; +use types::{ssz_tagged_signed_beacon_block, ssz_tagged_signed_beacon_block_arc}; +use types::{BeaconState, BlindedPayload, ChainSpec, Epoch, EthSpec, Hash256, SignedBeaconBlock}; + +/// This mirrors everything in the `AvailabilityPendingExecutedBlock`, except +/// that it is much smaller because it contains only a state root instead of +/// a full `BeaconState`. +#[derive(Encode, Decode, Clone)] +pub struct DietAvailabilityPendingExecutedBlock { + #[ssz(with = "ssz_tagged_signed_beacon_block_arc")] + block: Arc>, + state_root: Hash256, + #[ssz(with = "ssz_tagged_signed_beacon_block")] + parent_block: SignedBeaconBlock>, + parent_eth1_finalization_data: Eth1FinalizationData, + confirmed_state_roots: Vec, + consensus_context: ConsensusContext, + payload_verification_outcome: PayloadVerificationOutcome, +} + +/// just implementing the same methods as `AvailabilityPendingExecutedBlock` +impl DietAvailabilityPendingExecutedBlock { + pub fn as_block(&self) -> &SignedBeaconBlock { + &self.block + } + + pub fn num_blobs_expected(&self) -> usize { + self.block + .message() + .body() + .blob_kzg_commitments() + .map_or(0, |commitments| commitments.len()) + } +} + +/// This LRU cache holds BeaconStates used for block import. If the cache overflows, +/// the least recently used state will be dropped. If the dropped state is needed +/// later on, it will be recovered from the parent state and replaying the block. +/// +/// WARNING: This cache assumes the parent block of any `AvailabilityPendingExecutedBlock` +/// has already been imported into ForkChoice. If this is not the case, the cache +/// will fail to recover the state when the cache overflows because it can't load +/// the parent state! +pub struct StateLRUCache { + states: RwLock>>, + store: BeaconStore, + spec: ChainSpec, +} + +impl StateLRUCache { + pub fn new(store: BeaconStore, spec: ChainSpec) -> Self { + Self { + states: RwLock::new(LruCache::new(STATE_LRU_CAPACITY)), + store, + spec, + } + } + + /// This will store the state in the LRU cache and return a + /// `DietAvailabilityPendingExecutedBlock` which is much cheaper to + /// keep around in memory. + pub fn register_pending_executed_block( + &self, + executed_block: AvailabilityPendingExecutedBlock, + ) -> DietAvailabilityPendingExecutedBlock { + let state = executed_block.import_data.state; + let state_root = executed_block.block.state_root(); + self.states.write().put(state_root, state); + + DietAvailabilityPendingExecutedBlock { + block: executed_block.block, + state_root, + parent_block: executed_block.import_data.parent_block, + parent_eth1_finalization_data: executed_block.import_data.parent_eth1_finalization_data, + confirmed_state_roots: executed_block.import_data.confirmed_state_roots, + consensus_context: executed_block.import_data.consensus_context, + payload_verification_outcome: executed_block.payload_verification_outcome, + } + } + + /// Recover the `AvailabilityPendingExecutedBlock` from the diet version. + /// This method will first check the cache and if the state is not found + /// it will reconstruct the state by loading the parent state from disk and + /// replaying the block. + pub fn recover_pending_executed_block( + &self, + diet_executed_block: DietAvailabilityPendingExecutedBlock, + ) -> Result, AvailabilityCheckError> { + let maybe_state = self.states.write().pop(&diet_executed_block.state_root); + if let Some(state) = maybe_state { + let block_root = diet_executed_block.block.canonical_root(); + Ok(AvailabilityPendingExecutedBlock { + block: diet_executed_block.block, + import_data: BlockImportData { + block_root, + state, + parent_block: diet_executed_block.parent_block, + parent_eth1_finalization_data: diet_executed_block + .parent_eth1_finalization_data, + confirmed_state_roots: diet_executed_block.confirmed_state_roots, + consensus_context: diet_executed_block.consensus_context, + }, + payload_verification_outcome: diet_executed_block.payload_verification_outcome, + }) + } else { + self.reconstruct_pending_executed_block(diet_executed_block) + } + } + + /// Reconstruct the `AvailabilityPendingExecutedBlock` by loading the parent + /// state from disk and replaying the block. This function does NOT check the + /// LRU cache. + pub fn reconstruct_pending_executed_block( + &self, + diet_executed_block: DietAvailabilityPendingExecutedBlock, + ) -> Result, AvailabilityCheckError> { + let block_root = diet_executed_block.block.canonical_root(); + let state = self.reconstruct_state(&diet_executed_block)?; + Ok(AvailabilityPendingExecutedBlock { + block: diet_executed_block.block, + import_data: BlockImportData { + block_root, + state, + parent_block: diet_executed_block.parent_block, + parent_eth1_finalization_data: diet_executed_block.parent_eth1_finalization_data, + confirmed_state_roots: diet_executed_block.confirmed_state_roots, + consensus_context: diet_executed_block.consensus_context, + }, + payload_verification_outcome: diet_executed_block.payload_verification_outcome, + }) + } + + /// Reconstruct the state by loading the parent state from disk and replaying + /// the block. + fn reconstruct_state( + &self, + diet_executed_block: &DietAvailabilityPendingExecutedBlock, + ) -> Result, AvailabilityCheckError> { + let parent_block_root = diet_executed_block.parent_block.canonical_root(); + let parent_block_state_root = diet_executed_block.parent_block.state_root(); + let (parent_state_root, parent_state) = self + .store + .get_advanced_hot_state( + parent_block_root, + diet_executed_block.parent_block.slot(), + parent_block_state_root, + ) + .map_err(AvailabilityCheckError::StoreError)? + .ok_or(AvailabilityCheckError::ParentStateMissing( + parent_block_state_root, + ))?; + + let state_roots = vec![ + Ok((parent_state_root, diet_executed_block.parent_block.slot())), + Ok(( + diet_executed_block.state_root, + diet_executed_block.block.slot(), + )), + ]; + + let block_replayer: BlockReplayer<'_, T::EthSpec, AvailabilityCheckError, _> = + BlockReplayer::new(parent_state, &self.spec) + .no_signature_verification() + .state_processing_strategy(StateProcessingStrategy::Accurate) + .state_root_iter(state_roots.into_iter()) + .minimal_block_root_verification(); + + block_replayer + .apply_blocks(vec![diet_executed_block.block.clone_as_blinded()], None) + .map(|block_replayer| block_replayer.into_state()) + .and_then(|mut state| { + state + .build_exit_cache(&self.spec) + .map_err(AvailabilityCheckError::RebuildingStateCaches)?; + state + .update_tree_hash_cache() + .map_err(AvailabilityCheckError::RebuildingStateCaches)?; + Ok(state) + }) + } + + /// returns the state cache for inspection in tests + #[cfg(test)] + pub fn lru_cache(&self) -> &RwLock>> { + &self.states + } + + /// remove any states from the cache from before the given epoch + pub fn do_maintenance(&self, cutoff_epoch: Epoch) { + let mut write_lock = self.states.write(); + while let Some((_, state)) = write_lock.peek_lru() { + if state.slot().epoch(T::EthSpec::slots_per_epoch()) < cutoff_epoch { + write_lock.pop_lru(); + } else { + break; + } + } + } +} + +/// This can only be used during testing. The intended way to +/// obtain a `DietAvailabilityPendingExecutedBlock` is to call +/// `register_pending_executed_block` on the `StateLRUCache`. +#[cfg(test)] +impl From> + for DietAvailabilityPendingExecutedBlock +{ + fn from(value: AvailabilityPendingExecutedBlock) -> Self { + Self { + block: value.block, + state_root: value.import_data.state.canonical_root(), + parent_block: value.import_data.parent_block, + parent_eth1_finalization_data: value.import_data.parent_eth1_finalization_data, + confirmed_state_roots: value.import_data.confirmed_state_roots, + consensus_context: value.import_data.consensus_context, + payload_verification_outcome: value.payload_verification_outcome, + } + } +} diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index fe70f3c1ba1..9fe64d159f2 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -11,7 +11,7 @@ use beacon_chain::block_verification_types::AsBlock; use beacon_chain::store::Error; use beacon_chain::{ attestation_verification::{self, Error as AttnError, VerifiedAttestation}, - data_availability_checker::AvailabilityCheckError, + data_availability_checker::AvailabilityCheckErrorCategory, light_client_finality_update_verification::Error as LightClientFinalityUpdateError, light_client_optimistic_update_verification::Error as LightClientOptimisticUpdateError, observed_operations::ObservationOutcome, @@ -1233,24 +1233,15 @@ impl NetworkBeaconProcessor { ); } Err(BlockError::AvailabilityCheck(err)) => { - match err { - AvailabilityCheckError::KzgNotInitialized - | AvailabilityCheckError::Unexpected - | AvailabilityCheckError::SszTypes(_) - | AvailabilityCheckError::MissingBlobs - | AvailabilityCheckError::StoreError(_) - | AvailabilityCheckError::DecodeError(_) => { + match err.category() { + AvailabilityCheckErrorCategory::Internal => { warn!( self.log, "Internal availability check error"; "error" => ?err, ); } - AvailabilityCheckError::Kzg(_) - | AvailabilityCheckError::KzgVerificationFailed - | AvailabilityCheckError::KzgCommitmentMismatch { .. } - | AvailabilityCheckError::BlobIndexInvalid(_) - | AvailabilityCheckError::InconsistentBlobBlockRoots { .. } => { + AvailabilityCheckErrorCategory::Malicious => { // Note: we cannot penalize the peer that sent us the block // over gossip here because these errors imply either an issue // with: diff --git a/beacon_node/network/src/sync/block_lookups/mod.rs b/beacon_node/network/src/sync/block_lookups/mod.rs index 9b865fdfeec..fd9d0d6e0e4 100644 --- a/beacon_node/network/src/sync/block_lookups/mod.rs +++ b/beacon_node/network/src/sync/block_lookups/mod.rs @@ -13,7 +13,9 @@ use crate::sync::block_lookups::single_block_lookup::{ use crate::sync::manager::{Id, SingleLookupReqId}; use beacon_chain::block_verification_types::{AsBlock, RpcBlock}; pub use beacon_chain::data_availability_checker::ChildComponents; -use beacon_chain::data_availability_checker::{AvailabilityCheckError, DataAvailabilityChecker}; +use beacon_chain::data_availability_checker::{ + AvailabilityCheckErrorCategory, DataAvailabilityChecker, +}; use beacon_chain::validator_monitor::timestamp_now; use beacon_chain::{AvailabilityProcessingStatus, BeaconChainTypes, BlockError}; pub use common::Current; @@ -893,39 +895,25 @@ impl BlockLookups { ); return Ok(None); } - BlockError::AvailabilityCheck(e) => { - match e { - // Internal error. - AvailabilityCheckError::KzgNotInitialized - | AvailabilityCheckError::SszTypes(_) - | AvailabilityCheckError::MissingBlobs - | AvailabilityCheckError::StoreError(_) - | AvailabilityCheckError::DecodeError(_) - | AvailabilityCheckError::Unexpected => { - warn!(self.log, "Internal availability check failure"; "root" => %root, "peer_id" => %peer_id, "error" => ?e); - lookup - .block_request_state - .state - .register_failure_downloading(); - lookup - .blob_request_state - .state - .register_failure_downloading(); - lookup.request_block_and_blobs(cx)? - } - - // Malicious errors. - AvailabilityCheckError::Kzg(_) - | AvailabilityCheckError::BlobIndexInvalid(_) - | AvailabilityCheckError::KzgCommitmentMismatch { .. } - | AvailabilityCheckError::KzgVerificationFailed - | AvailabilityCheckError::InconsistentBlobBlockRoots { .. } => { - warn!(self.log, "Availability check failure"; "root" => %root, "peer_id" => %peer_id, "error" => ?e); - lookup.handle_availability_check_failure(cx); - lookup.request_block_and_blobs(cx)? - } + BlockError::AvailabilityCheck(e) => match e.category() { + AvailabilityCheckErrorCategory::Internal => { + warn!(self.log, "Internal availability check failure"; "root" => %root, "peer_id" => %peer_id, "error" => ?e); + lookup + .block_request_state + .state + .register_failure_downloading(); + lookup + .blob_request_state + .state + .register_failure_downloading(); + lookup.request_block_and_blobs(cx)? } - } + AvailabilityCheckErrorCategory::Malicious => { + warn!(self.log, "Availability check failure"; "root" => %root, "peer_id" => %peer_id, "error" => ?e); + lookup.handle_availability_check_failure(cx); + lookup.request_block_and_blobs(cx)? + } + }, other => { warn!(self.log, "Peer sent invalid block in single block lookup"; "root" => %root, "error" => ?other, "peer_id" => %peer_id); if let Ok(block_peer) = lookup.block_request_state.state.processing_peer() { diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index f0ba15ca89c..82a5d305d1e 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -1946,80 +1946,3 @@ impl ForkVersionDeserialize for BeaconState { )) } } - -/// This module can be used to encode and decode a `BeaconState` the same way it -/// would be done if we had tagged the superstruct enum with -/// `#[ssz(enum_behaviour = "union")]` -/// This should _only_ be used for *some* cases to store these objects in the -/// database and _NEVER_ for encoding / decoding states sent over the network! -pub mod ssz_tagged_beacon_state { - use super::*; - pub mod encode { - use super::*; - #[allow(unused_imports)] - use ssz::*; - - pub fn is_ssz_fixed_len() -> bool { - false - } - - pub fn ssz_fixed_len() -> usize { - BYTES_PER_LENGTH_OFFSET - } - - pub fn ssz_bytes_len(state: &BeaconState) -> usize { - state - .ssz_bytes_len() - .checked_add(1) - .expect("encoded length must be less than usize::max") - } - - pub fn ssz_append(state: &BeaconState, buf: &mut Vec) { - let fork_name = state.fork_name_unchecked(); - fork_name.ssz_append(buf); - state.ssz_append(buf); - } - - pub fn as_ssz_bytes(state: &BeaconState) -> Vec { - let mut buf = vec![]; - ssz_append(state, &mut buf); - - buf - } - } - - pub mod decode { - use super::*; - #[allow(unused_imports)] - use ssz::*; - - pub fn is_ssz_fixed_len() -> bool { - false - } - - pub fn ssz_fixed_len() -> usize { - BYTES_PER_LENGTH_OFFSET - } - - pub fn from_ssz_bytes(bytes: &[u8]) -> Result, DecodeError> { - let fork_byte = bytes - .first() - .copied() - .ok_or(DecodeError::OutOfBoundsByte { i: 0 })?; - let body = bytes - .get(1..) - .ok_or(DecodeError::OutOfBoundsByte { i: 1 })?; - match ForkName::from_ssz_bytes(&[fork_byte])? { - ForkName::Base => Ok(BeaconState::Base(BeaconStateBase::from_ssz_bytes(body)?)), - ForkName::Altair => Ok(BeaconState::Altair(BeaconStateAltair::from_ssz_bytes( - body, - )?)), - ForkName::Merge => Ok(BeaconState::Merge(BeaconStateMerge::from_ssz_bytes(body)?)), - ForkName::Capella => Ok(BeaconState::Capella(BeaconStateCapella::from_ssz_bytes( - body, - )?)), - ForkName::Deneb => Ok(BeaconState::Deneb(BeaconStateDeneb::from_ssz_bytes(body)?)), - } - } - } -} From d9acee5a7260bf2e63ea20ddbf494f0e4d4a59b6 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Thu, 12 Oct 2023 03:49:08 +1100 Subject: [PATCH 05/21] Delete unused ssz_types file (#4824) --- .../src/serde_utils/list_of_hex_fixed_vec.rs | 77 ------------------- 1 file changed, 77 deletions(-) delete mode 100644 consensus/ssz_types/src/serde_utils/list_of_hex_fixed_vec.rs diff --git a/consensus/ssz_types/src/serde_utils/list_of_hex_fixed_vec.rs b/consensus/ssz_types/src/serde_utils/list_of_hex_fixed_vec.rs deleted file mode 100644 index b93c869067c..00000000000 --- a/consensus/ssz_types/src/serde_utils/list_of_hex_fixed_vec.rs +++ /dev/null @@ -1,77 +0,0 @@ -//! Serialize `VariableList, N>` as list of 0x-prefixed hex string. -use crate::{FixedVector, VariableList}; -use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer}; -use std::marker::PhantomData; -use typenum::Unsigned; - -#[derive(Deserialize)] -#[serde(transparent)] -pub struct WrappedListOwned( - #[serde(with = "crate::serde_utils::hex_fixed_vec")] FixedVector, -); - -#[derive(Serialize)] -#[serde(transparent)] -pub struct WrappedListRef<'a, N: Unsigned>( - #[serde(with = "crate::serde_utils::hex_fixed_vec")] &'a FixedVector, -); - -pub fn serialize( - list: &VariableList, N>, - serializer: S, -) -> Result -where - S: Serializer, - M: Unsigned, - N: Unsigned, -{ - let mut seq = serializer.serialize_seq(Some(list.len()))?; - for bytes in list { - seq.serialize_element(&WrappedListRef(bytes))?; - } - seq.end() -} - -#[derive(Default)] -pub struct Visitor { - _phantom_m: PhantomData, - _phantom_n: PhantomData, -} - -impl<'a, M, N> serde::de::Visitor<'a> for Visitor -where - M: Unsigned, - N: Unsigned, -{ - type Value = VariableList, N>; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(formatter, "a list of 0x-prefixed hex bytes") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'a>, - { - let mut list: VariableList, N> = <_>::default(); - - while let Some(val) = seq.next_element::>()? { - list.push(val.0).map_err(|e| { - serde::de::Error::custom(format!("failed to push value to list: {:?}.", e)) - })?; - } - - Ok(list) - } -} - -pub fn deserialize<'de, D, M, N>( - deserializer: D, -) -> Result, N>, D::Error> -where - D: Deserializer<'de>, - M: Unsigned, - N: Unsigned, -{ - deserializer.deserialize_seq(Visitor::default()) -} From 4555e3304847b1814bf9020e428086fe9600628d Mon Sep 17 00:00:00 2001 From: realbigsean Date: Wed, 11 Oct 2023 13:01:30 -0400 Subject: [PATCH 06/21] Remove serde derive references (#4830) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * remove remaining uses of serde_derive * fix lockfile --------- Co-authored-by: João Oliveira --- Cargo.lock | 285 ++++++++---------- beacon_node/client/Cargo.toml | 1 - beacon_node/client/src/config.rs | 2 +- beacon_node/lighthouse_network/Cargo.toml | 1 - beacon_node/lighthouse_network/src/config.rs | 2 +- .../lighthouse_network/src/rpc/config.rs | 2 +- .../src/rpc/rate_limiter.rs | 2 +- .../lighthouse_network/src/types/topics.rs | 2 +- beacon_node/operation_pool/Cargo.toml | 1 - .../operation_pool/src/attestation_id.rs | 2 +- .../operation_pool/src/sync_aggregate_id.rs | 2 +- beacon_node/store/Cargo.toml | 1 - beacon_node/store/src/config.rs | 2 +- beacon_node/store/src/hot_cold_store.rs | 2 +- beacon_node/store/src/metadata.rs | 2 +- boot_node/Cargo.toml | 1 - boot_node/src/config.rs | 2 +- common/account_utils/Cargo.toml | 1 - common/account_utils/src/lib.rs | 2 +- .../src/validator_definitions.rs | 2 +- common/eth2_interop_keypairs/Cargo.toml | 1 - common/eth2_interop_keypairs/src/lib.rs | 2 +- common/monitoring_api/Cargo.toml | 1 - common/monitoring_api/src/types.rs | 2 +- common/system_health/Cargo.toml | 1 - consensus/proto_array/Cargo.toml | 3 +- .../src/fork_choice_test_definition.rs | 2 +- consensus/proto_array/src/proto_array.rs | 2 +- .../src/proto_array_fork_choice.rs | 2 +- consensus/types/Cargo.toml | 1 - consensus/types/src/aggregate_and_proof.rs | 2 +- consensus/types/src/attestation.rs | 2 +- consensus/types/src/attestation_data.rs | 2 +- consensus/types/src/attestation_duty.rs | 2 +- consensus/types/src/attester_slashing.rs | 2 +- consensus/types/src/beacon_block.rs | 2 +- consensus/types/src/beacon_block_body.rs | 2 +- consensus/types/src/beacon_block_header.rs | 2 +- consensus/types/src/beacon_state.rs | 2 +- .../types/src/beacon_state/committee_cache.rs | 2 +- .../types/src/beacon_state/exit_cache.rs | 2 +- .../progressive_balances_cache.rs | 2 +- .../types/src/beacon_state/pubkey_cache.rs | 2 +- consensus/types/src/blob_sidecar.rs | 2 +- .../types/src/bls_to_execution_change.rs | 2 +- consensus/types/src/builder_bid.rs | 3 +- consensus/types/src/chain_spec.rs | 2 +- consensus/types/src/checkpoint.rs | 2 +- consensus/types/src/config_and_preset.rs | 2 +- consensus/types/src/contribution_and_proof.rs | 2 +- consensus/types/src/deposit.rs | 2 +- consensus/types/src/deposit_data.rs | 2 +- consensus/types/src/deposit_message.rs | 2 +- consensus/types/src/deposit_tree_snapshot.rs | 2 +- consensus/types/src/enr_fork_id.rs | 2 +- consensus/types/src/eth1_data.rs | 2 +- consensus/types/src/eth_spec.rs | 2 +- consensus/types/src/execution_block_hash.rs | 2 +- consensus/types/src/execution_payload.rs | 2 +- .../types/src/execution_payload_header.rs | 2 +- consensus/types/src/fork.rs | 2 +- consensus/types/src/fork_data.rs | 2 +- consensus/types/src/fork_name.rs | 2 +- consensus/types/src/historical_batch.rs | 2 +- consensus/types/src/historical_summary.rs | 2 +- consensus/types/src/indexed_attestation.rs | 2 +- consensus/types/src/light_client_bootstrap.rs | 2 +- .../types/src/light_client_finality_update.rs | 2 +- .../src/light_client_optimistic_update.rs | 2 +- consensus/types/src/light_client_update.rs | 2 +- consensus/types/src/participation_flags.rs | 2 +- consensus/types/src/pending_attestation.rs | 2 +- consensus/types/src/preset.rs | 2 +- consensus/types/src/proposer_slashing.rs | 2 +- consensus/types/src/shuffling_id.rs | 2 +- .../types/src/signed_aggregate_and_proof.rs | 2 +- consensus/types/src/signed_beacon_block.rs | 2 +- .../types/src/signed_beacon_block_header.rs | 2 +- consensus/types/src/signed_blob.rs | 2 +- .../src/signed_bls_to_execution_change.rs | 2 +- .../src/signed_contribution_and_proof.rs | 2 +- consensus/types/src/signed_voluntary_exit.rs | 2 +- consensus/types/src/signing_data.rs | 2 +- consensus/types/src/slot_epoch.rs | 2 +- consensus/types/src/subnet_id.rs | 2 +- consensus/types/src/sync_aggregate.rs | 2 +- .../src/sync_aggregator_selection_data.rs | 2 +- consensus/types/src/sync_committee.rs | 2 +- .../types/src/sync_committee_contribution.rs | 2 +- consensus/types/src/sync_committee_message.rs | 2 +- consensus/types/src/sync_duty.rs | 2 +- consensus/types/src/sync_subnet_id.rs | 2 +- consensus/types/src/validator.rs | 2 +- consensus/types/src/voluntary_exit.rs | 2 +- consensus/types/src/withdrawal.rs | 2 +- crypto/bls/Cargo.toml | 1 - crypto/bls/src/zeroize_hash.rs | 2 +- crypto/kzg/src/lib.rs | 2 +- lighthouse/environment/Cargo.toml | 1 - lighthouse/environment/src/lib.rs | 2 +- slasher/Cargo.toml | 1 - slasher/src/array.rs | 2 +- slasher/src/config.rs | 2 +- testing/ef_tests/Cargo.toml | 1 - .../ef_tests/src/cases/bls_aggregate_sigs.rs | 2 +- .../src/cases/bls_aggregate_verify.rs | 2 +- .../ef_tests/src/cases/bls_batch_verify.rs | 2 +- .../src/cases/bls_eth_aggregate_pubkeys.rs | 2 +- .../cases/bls_eth_fast_aggregate_verify.rs | 2 +- .../src/cases/bls_fast_aggregate_verify.rs | 2 +- testing/ef_tests/src/cases/bls_sign_msg.rs | 2 +- testing/ef_tests/src/cases/bls_verify_msg.rs | 2 +- testing/ef_tests/src/cases/common.rs | 2 +- .../ef_tests/src/cases/epoch_processing.rs | 2 +- testing/ef_tests/src/cases/fork.rs | 2 +- .../src/cases/genesis_initialization.rs | 2 +- .../ef_tests/src/cases/genesis_validity.rs | 2 +- .../src/cases/kzg_blob_to_kzg_commitment.rs | 2 +- .../src/cases/kzg_compute_blob_kzg_proof.rs | 2 +- .../src/cases/kzg_compute_kzg_proof.rs | 2 +- .../src/cases/kzg_verify_blob_kzg_proof.rs | 2 +- .../cases/kzg_verify_blob_kzg_proof_batch.rs | 2 +- .../src/cases/kzg_verify_kzg_proof.rs | 2 +- .../src/cases/merkle_proof_validity.rs | 2 +- testing/ef_tests/src/cases/operations.rs | 2 +- testing/ef_tests/src/cases/rewards.rs | 2 +- testing/ef_tests/src/cases/sanity_blocks.rs | 2 +- testing/ef_tests/src/cases/sanity_slots.rs | 2 +- testing/ef_tests/src/cases/shuffling.rs | 2 +- testing/ef_tests/src/cases/ssz_generic.rs | 2 +- testing/ef_tests/src/cases/ssz_static.rs | 2 +- testing/ef_tests/src/cases/transition.rs | 2 +- validator_client/Cargo.toml | 1 - .../slashing_protection/Cargo.toml | 1 - .../slashing_protection/src/interchange.rs | 2 +- .../src/interchange_test.rs | 2 +- validator_client/src/config.rs | 2 +- validator_client/src/graffiti_file.rs | 2 +- 138 files changed, 250 insertions(+), 295 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 83d4cd74e3c..979921faedd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,7 +53,6 @@ dependencies = [ "regex", "rpassword", "serde", - "serde_derive", "serde_yaml", "slog", "types", @@ -153,9 +152,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -287,7 +286,7 @@ dependencies = [ "log", "parking", "polling", - "rustix 0.37.23", + "rustix 0.37.24", "slab", "socket2 0.4.9", "waker-fn", @@ -310,7 +309,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -647,7 +646,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.37", + "syn 2.0.38", "which", ] @@ -735,7 +734,6 @@ dependencies = [ "milagro_bls", "rand", "serde", - "serde_derive", "tree_hash", "zeroize", ] @@ -776,7 +774,6 @@ dependencies = [ "log", "logging", "serde", - "serde_derive", "serde_json", "serde_yaml", "slog", @@ -829,9 +826,9 @@ checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" @@ -917,9 +914,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" +checksum = "12024c4645c97566567129c204f65d5815a8c9aecf30fcbe682b2fe034996d36" dependencies = [ "serde", ] @@ -1095,7 +1092,6 @@ dependencies = [ "parking_lot 0.12.1", "sensitive_url", "serde", - "serde_derive", "serde_yaml", "slasher", "slasher_service", @@ -1345,9 +1341,9 @@ dependencies = [ [[package]] name = "csv" -version = "1.2.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626ae34994d3d8d668f4269922248239db4ae42d538b14c398b74a52208e8086" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" dependencies = [ "csv-core", "itoa", @@ -1357,9 +1353,9 @@ dependencies = [ [[package]] name = "csv-core" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" dependencies = [ "memchr", ] @@ -1421,7 +1417,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1613,7 +1609,7 @@ checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1631,9 +1627,9 @@ dependencies = [ [[package]] name = "diesel" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c8a2cb22327206568569e5a45bb5a2c946455efdd76e24d15b7e82171af95e" +checksum = "2268a214a6f118fce1838edba3d1561cf0e78d8de785475957a580a7f8c69d33" dependencies = [ "bitflags 2.4.0", "byteorder", @@ -1652,7 +1648,7 @@ dependencies = [ "diesel_table_macro_syntax", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1672,7 +1668,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" dependencies = [ - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1787,7 +1783,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -1822,7 +1818,7 @@ checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" dependencies = [ "der 0.7.8", "digest 0.10.7", - "elliptic-curve 0.13.5", + "elliptic-curve 0.13.6", "rfc6979 0.4.0", "signature 2.1.0", "spki 0.7.2", @@ -1848,7 +1844,7 @@ dependencies = [ "ed25519", "rand_core 0.6.4", "serde", - "sha2 0.10.7", + "sha2 0.10.8", "zeroize", ] @@ -1874,7 +1870,6 @@ dependencies = [ "kzg", "rayon", "serde", - "serde_derive", "serde_json", "serde_repr", "serde_yaml", @@ -1914,9 +1909,9 @@ dependencies = [ [[package]] name = "elliptic-curve" -version = "0.13.5" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +checksum = "d97ca172ae9dc9f9b779a6e3a65d308f2af74e5b8c921299075bdb4a0370e914" dependencies = [ "base16ct 0.2.0", "crypto-bigint 0.5.3", @@ -2006,7 +2001,6 @@ dependencies = [ "futures", "logging", "serde", - "serde_derive", "slog", "slog-async", "slog-json", @@ -2034,25 +2028,14 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ - "errno-dragonfly", "libc", "windows-sys 0.48.0", ] -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "error-chain" version = "0.12.4" @@ -2161,7 +2144,6 @@ dependencies = [ "lazy_static", "num-bigint", "serde", - "serde_derive", "serde_yaml", ] @@ -2333,7 +2315,7 @@ dependencies = [ "impl-codec 0.6.0", "impl-rlp", "impl-serde 0.4.0", - "primitive-types 0.12.1", + "primitive-types 0.12.2", "scale-info", "uint", ] @@ -2347,7 +2329,7 @@ dependencies = [ "cpufeatures", "lazy_static", "ring", - "sha2 0.10.7", + "sha2 0.10.8", ] [[package]] @@ -2866,7 +2848,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -3126,9 +3108,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" dependencies = [ "ahash 0.8.3", "allocator-api2", @@ -3158,7 +3140,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.0", + "hashbrown 0.14.1", ] [[package]] @@ -3633,12 +3615,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.1", ] [[package]] @@ -3757,9 +3739,9 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ "libc", ] @@ -3796,7 +3778,7 @@ dependencies = [ "cfg-if", "ecdsa 0.14.8", "elliptic-curve 0.12.3", - "sha2 0.10.7", + "sha2 0.10.8", "sha3 0.10.8", ] @@ -3808,9 +3790,9 @@ checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" dependencies = [ "cfg-if", "ecdsa 0.16.8", - "elliptic-curve 0.13.5", + "elliptic-curve 0.13.6", "once_cell", - "sha2 0.10.7", + "sha2 0.10.8", "signature 2.1.0", ] @@ -3829,7 +3811,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b286e6b663fb926e1eeb68528e69cb70ed46c6d65871a21b2215ae8154c6d3c" dependencies = [ - "primitive-types 0.12.1", + "primitive-types 0.12.2", "tiny-keccak", ] @@ -3930,9 +3912,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.148" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libflate" @@ -3966,9 +3948,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libmdbx" @@ -4108,7 +4090,7 @@ dependencies = [ "quick-protobuf-codec", "rand", "regex", - "sha2 0.10.7", + "sha2 0.10.8", "smallvec", "unsigned-varint 0.7.2", "void", @@ -4138,13 +4120,14 @@ dependencies = [ [[package]] name = "libp2p-identity" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686e73aff5e23efbb99bc85340ea6fd8686986aa7b283a881ba182cfca535ca9" +checksum = "57bf6e730ec5e7022958da53ffb03b326e681b7316939012ae9b3c7449a812d4" dependencies = [ "asn1_der", "bs58 0.5.0", "ed25519-dalek", + "hkdf", "libsecp256k1", "log", "multihash", @@ -4152,7 +4135,7 @@ dependencies = [ "quick-protobuf", "rand", "sec1 0.7.3", - "sha2 0.10.7", + "sha2 0.10.8", "thiserror", "void", "zeroize", @@ -4231,7 +4214,7 @@ dependencies = [ "once_cell", "quick-protobuf", "rand", - "sha2 0.10.7", + "sha2 0.10.8", "snow", "static_assertions", "thiserror", @@ -4280,9 +4263,9 @@ dependencies = [ [[package]] name = "libp2p-swarm" -version = "0.43.4" +version = "0.43.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0cf749abdc5ca1dce6296dc8ea0f012464dfcfd3ddd67ffc0cabd8241c4e1da" +checksum = "ab94183f8fc2325817835b57946deb44340c99362cd4606c0a5717299b2ba369" dependencies = [ "either", "fnv", @@ -4311,7 +4294,7 @@ dependencies = [ "proc-macro-warning", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -4514,7 +4497,6 @@ dependencies = [ "rand", "regex", "serde", - "serde_derive", "sha2 0.9.9", "slog", "slog-async", @@ -4567,9 +4549,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.7" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "lmdb-rkv" @@ -4752,9 +4734,9 @@ checksum = "8c408dc227d302f1496c84d9dc68c00fec6f56f9228a18f3023f976f3ca7c945" [[package]] name = "memchr" -version = "2.6.3" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memoffset" @@ -4902,7 +4884,6 @@ dependencies = [ "reqwest", "sensitive_url", "serde", - "serde_derive", "serde_json", "slog", "store", @@ -5242,9 +5223,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -5359,7 +5340,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -5407,7 +5388,6 @@ dependencies = [ "rand", "rayon", "serde", - "serde_derive", "state_processing", "store", "tokio", @@ -5427,9 +5407,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" dependencies = [ "ecdsa 0.16.8", - "elliptic-curve 0.13.5", + "elliptic-curve 0.13.6", "primeorder", - "sha2 0.10.7", + "sha2 0.10.8", ] [[package]] @@ -5573,7 +5553,7 @@ dependencies = [ "digest 0.10.7", "hmac 0.12.1", "password-hash", - "sha2 0.10.7", + "sha2 0.10.8", ] [[package]] @@ -5651,7 +5631,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -5785,7 +5765,7 @@ dependencies = [ "md-5", "memchr", "rand", - "sha2 0.10.7", + "sha2 0.10.8", "stringprep", ] @@ -5830,7 +5810,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -5839,7 +5819,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3" dependencies = [ - "elliptic-curve 0.13.5", + "elliptic-curve 0.13.6", ] [[package]] @@ -5857,9 +5837,9 @@ dependencies = [ [[package]] name = "primitive-types" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash 0.8.0", "impl-codec 0.6.0", @@ -5871,12 +5851,12 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.1.3" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ - "thiserror", - "toml 0.5.11", + "once_cell", + "toml_edit", ] [[package]] @@ -5917,14 +5897,14 @@ checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -5979,7 +5959,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -5990,7 +5970,6 @@ dependencies = [ "ethereum_ssz_derive", "safe_arith", "serde", - "serde_derive", "serde_yaml", "superstruct", "types", @@ -6272,14 +6251,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.8", - "regex-syntax 0.7.5", + "regex-automata 0.4.1", + "regex-syntax 0.8.1", ] [[package]] @@ -6293,13 +6272,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.5", + "regex-syntax 0.8.1", ] [[package]] @@ -6310,15 +6289,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "56d84fdd47036b038fc80dd333d10b6aab10d5d31f4a366e20014def75328d33" [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ "base64 0.21.4", "bytes", @@ -6344,6 +6323,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls", @@ -6522,9 +6502,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.23" +version = "0.37.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +checksum = "4279d76516df406a8bd37e7dff53fd37d1a093f997a3c34a5c21658c126db06d" dependencies = [ "bitflags 1.3.2", "errno", @@ -6536,14 +6516,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.14" +version = "0.38.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" +checksum = "5a74ee2d7c2581cd139b42447d7d9389b889bdaad3a73f1ebb16f2a3237bb19c" dependencies = [ "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys 0.4.7", + "linux-raw-sys 0.4.10", "windows-sys 0.48.0", ] @@ -6752,9 +6732,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" dependencies = [ "serde", ] @@ -6810,7 +6790,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -6842,7 +6822,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -6926,9 +6906,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -6959,9 +6939,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -7069,7 +7049,6 @@ dependencies = [ "rayon", "safe_arith", "serde", - "serde_derive", "slog", "sloggers", "strum", @@ -7109,7 +7088,6 @@ dependencies = [ "rayon", "rusqlite", "serde", - "serde_derive", "serde_json", "tempfile", "types", @@ -7252,7 +7230,7 @@ dependencies = [ "rand_core 0.6.4", "ring", "rustc_version", - "sha2 0.10.7", + "sha2 0.10.8", "subtle", ] @@ -7381,7 +7359,6 @@ dependencies = [ "lru 0.7.8", "parking_lot 0.12.1", "serde", - "serde_derive", "slog", "sloggers", "state_processing", @@ -7477,9 +7454,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.37" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -7547,7 +7524,6 @@ dependencies = [ "lighthouse_network", "parking_lot 0.12.1", "serde", - "serde_derive", "serde_json", "sysinfo", "types", @@ -7600,7 +7576,7 @@ dependencies = [ "cfg-if", "fastrand 2.0.1", "redox_syscall 0.3.5", - "rustix 0.38.14", + "rustix 0.38.18", "windows-sys 0.48.0", ] @@ -7653,7 +7629,7 @@ dependencies = [ "rand", "serde", "serde_json", - "sha2 0.10.7", + "sha2 0.10.8", ] [[package]] @@ -7667,22 +7643,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -7757,7 +7733,7 @@ dependencies = [ "pbkdf2 0.11.0", "rand", "rustc-hash", - "sha2 0.10.7", + "sha2 0.10.8", "thiserror", "unicode-normalization", "wasm-bindgen", @@ -7800,9 +7776,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" dependencies = [ "backtrace", "bytes", @@ -7834,7 +7810,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -7962,7 +7938,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.0.2", "serde", "serde_spanned", "toml_datetime", @@ -8018,7 +7994,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -8215,7 +8191,6 @@ dependencies = [ "rusqlite", "safe_arith", "serde", - "serde_derive", "serde_json", "serde_yaml", "slog", @@ -8396,7 +8371,6 @@ dependencies = [ "safe_arith", "sensitive_url", "serde", - "serde_derive", "serde_json", "slashing_protection", "slog", @@ -8515,8 +8489,8 @@ dependencies = [ [[package]] name = "warp" -version = "0.3.5" -source = "git+https://github.com/seanmonstar/warp.git#5ad8a9cb155f6485d13d591a564d8c70053a388a" +version = "0.3.6" +source = "git+https://github.com/seanmonstar/warp.git#efe8548a19172e69918396d0fdbc369df9d0eb17" dependencies = [ "bytes", "futures-channel", @@ -8593,7 +8567,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", "wasm-bindgen-shared", ] @@ -8627,7 +8601,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8752,7 +8726,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.14", + "rustix 0.38.18", ] [[package]] @@ -9012,9 +8986,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "037711d82167854aff2018dfd193aa0fef5370f456732f0d5a0c59b0f1b4b907" dependencies = [ "memchr", ] @@ -9156,7 +9130,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.38", ] [[package]] @@ -9200,11 +9174,10 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.8+zstd.1.5.5" +version = "2.0.9+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" dependencies = [ "cc", - "libc", "pkg-config", ] diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index b60748e30c6..26c53154e3b 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -22,7 +22,6 @@ types = { workspace = true } eth2_config = { workspace = true } slot_clock = { workspace = true } serde = { workspace = true } -serde_derive = "1.0.116" error-chain = { workspace = true } slog = { workspace = true } tokio = { workspace = true } diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index 282eacc15a1..8b47d0fc622 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -5,7 +5,7 @@ use directory::DEFAULT_ROOT_DIR; use environment::LoggerConfig; use network::NetworkConfig; use sensitive_url::SensitiveUrl; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::fs; use std::path::PathBuf; use std::time::Duration; diff --git a/beacon_node/lighthouse_network/Cargo.toml b/beacon_node/lighthouse_network/Cargo.toml index 07674fb6dd2..125bbe9bc2f 100644 --- a/beacon_node/lighthouse_network/Cargo.toml +++ b/beacon_node/lighthouse_network/Cargo.toml @@ -10,7 +10,6 @@ unsigned-varint = { version = "0.6", features = ["codec"] } ssz_types = { workspace = true } types = { workspace = true } serde = { workspace = true } -serde_derive = "1" ethereum_ssz = { workspace = true } ethereum_ssz_derive = { workspace = true } tree_hash = { workspace = true } diff --git a/beacon_node/lighthouse_network/src/config.rs b/beacon_node/lighthouse_network/src/config.rs index 472f3ef75e7..fb005e08943 100644 --- a/beacon_node/lighthouse_network/src/config.rs +++ b/beacon_node/lighthouse_network/src/config.rs @@ -8,7 +8,7 @@ use directory::{ use discv5::{Discv5Config, Discv5ConfigBuilder}; use libp2p::gossipsub; use libp2p::Multiaddr; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::net::{Ipv4Addr, Ipv6Addr}; use std::num::NonZeroU16; diff --git a/beacon_node/lighthouse_network/src/rpc/config.rs b/beacon_node/lighthouse_network/src/rpc/config.rs index 2fcb380bb65..ad967311417 100644 --- a/beacon_node/lighthouse_network/src/rpc/config.rs +++ b/beacon_node/lighthouse_network/src/rpc/config.rs @@ -6,7 +6,7 @@ use std::{ use super::{methods, rate_limiter::Quota, Protocol}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; /// Auxiliary struct to aid on configuration parsing. /// diff --git a/beacon_node/lighthouse_network/src/rpc/rate_limiter.rs b/beacon_node/lighthouse_network/src/rpc/rate_limiter.rs index 1031b66e2d9..0b57374e8b6 100644 --- a/beacon_node/lighthouse_network/src/rpc/rate_limiter.rs +++ b/beacon_node/lighthouse_network/src/rpc/rate_limiter.rs @@ -2,7 +2,7 @@ use super::config::RateLimiterConfig; use crate::rpc::Protocol; use fnv::FnvHashMap; use libp2p::PeerId; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::convert::TryInto; use std::future::Future; use std::hash::Hash; diff --git a/beacon_node/lighthouse_network/src/types/topics.rs b/beacon_node/lighthouse_network/src/types/topics.rs index 4099949ac37..e7e771e1adc 100644 --- a/beacon_node/lighthouse_network/src/types/topics.rs +++ b/beacon_node/lighthouse_network/src/types/topics.rs @@ -1,5 +1,5 @@ use libp2p::gossipsub::{IdentTopic as Topic, TopicHash}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use strum::AsRefStr; use types::consts::deneb::BLOB_SIDECAR_SUBNET_COUNT; use types::{EthSpec, ForkName, SubnetId, SyncSubnetId}; diff --git a/beacon_node/operation_pool/Cargo.toml b/beacon_node/operation_pool/Cargo.toml index afdbd7257a0..dd8cd9c49ed 100644 --- a/beacon_node/operation_pool/Cargo.toml +++ b/beacon_node/operation_pool/Cargo.toml @@ -16,7 +16,6 @@ ethereum_ssz = { workspace = true } ethereum_ssz_derive = { workspace = true } rayon = { workspace = true } serde = { workspace = true } -serde_derive = "1.0.116" store = { workspace = true } bitvec = { workspace = true } rand = { workspace = true } diff --git a/beacon_node/operation_pool/src/attestation_id.rs b/beacon_node/operation_pool/src/attestation_id.rs index b65975787eb..f0dc6536a54 100644 --- a/beacon_node/operation_pool/src/attestation_id.rs +++ b/beacon_node/operation_pool/src/attestation_id.rs @@ -1,4 +1,4 @@ -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; /// Serialized `AttestationData` augmented with a domain to encode the fork info. diff --git a/beacon_node/operation_pool/src/sync_aggregate_id.rs b/beacon_node/operation_pool/src/sync_aggregate_id.rs index 401e0c5f82f..40d6e36490e 100644 --- a/beacon_node/operation_pool/src/sync_aggregate_id.rs +++ b/beacon_node/operation_pool/src/sync_aggregate_id.rs @@ -1,4 +1,4 @@ -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use types::{Hash256, Slot}; diff --git a/beacon_node/store/Cargo.toml b/beacon_node/store/Cargo.toml index 32c3868294f..7bf1ef76bef 100644 --- a/beacon_node/store/Cargo.toml +++ b/beacon_node/store/Cargo.toml @@ -19,7 +19,6 @@ types = { workspace = true } state_processing = { workspace = true } slog = { workspace = true } serde = { workspace = true } -serde_derive = "1.0.116" lazy_static = { workspace = true } lighthouse_metrics = { workspace = true } lru = { workspace = true } diff --git a/beacon_node/store/src/config.rs b/beacon_node/store/src/config.rs index 2ff433f11b4..4e516b091bc 100644 --- a/beacon_node/store/src/config.rs +++ b/beacon_node/store/src/config.rs @@ -1,5 +1,5 @@ use crate::{DBColumn, Error, StoreItem}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use types::{EthSpec, MinimalEthSpec}; diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 81975340728..91b03f9f5e2 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -25,7 +25,7 @@ use itertools::process_results; use leveldb::iterator::LevelDBIterator; use lru::LruCache; use parking_lot::{Mutex, RwLock}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use slog::{debug, error, info, trace, warn, Logger}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; diff --git a/beacon_node/store/src/metadata.rs b/beacon_node/store/src/metadata.rs index 59a607aaf76..124c9a2f586 100644 --- a/beacon_node/store/src/metadata.rs +++ b/beacon_node/store/src/metadata.rs @@ -1,5 +1,5 @@ use crate::{DBColumn, Error, StoreItem}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use types::{Checkpoint, Hash256, Slot}; diff --git a/boot_node/Cargo.toml b/boot_node/Cargo.toml index 0c815ca9a7a..553c8ab8676 100644 --- a/boot_node/Cargo.toml +++ b/boot_node/Cargo.toml @@ -21,7 +21,6 @@ slog-scope = "4.3.0" slog-stdlog = "4.0.0" hex = { workspace = true } serde = { workspace = true } -serde_derive = "1.0.116" serde_json = { workspace = true } serde_yaml = { workspace = true } eth2_network_config = { workspace = true } diff --git a/boot_node/src/config.rs b/boot_node/src/config.rs index d435efc6f5a..6cfa8f4cf76 100644 --- a/boot_node/src/config.rs +++ b/boot_node/src/config.rs @@ -7,7 +7,7 @@ use lighthouse_network::{ discovery::{load_enr_from_disk, use_or_load_enr}, load_private_key, CombinedKeyExt, NetworkConfig, }; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz::Encode; use std::net::{SocketAddrV4, SocketAddrV6}; use std::time::Duration; diff --git a/common/account_utils/Cargo.toml b/common/account_utils/Cargo.toml index 10113ab3206..e66bf14233a 100644 --- a/common/account_utils/Cargo.toml +++ b/common/account_utils/Cargo.toml @@ -13,7 +13,6 @@ eth2_keystore = { workspace = true } filesystem = { workspace = true } zeroize = { workspace = true } serde = { workspace = true } -serde_derive = "1.0.116" serde_yaml = { workspace = true } slog = { workspace = true } types = { workspace = true } diff --git a/common/account_utils/src/lib.rs b/common/account_utils/src/lib.rs index e566d7cdda3..8707ae531f7 100644 --- a/common/account_utils/src/lib.rs +++ b/common/account_utils/src/lib.rs @@ -8,7 +8,7 @@ use eth2_wallet::{ }; use filesystem::{create_with_600_perms, Error as FsError}; use rand::{distributions::Alphanumeric, Rng}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::fs::{self, File}; use std::io; use std::io::prelude::*; diff --git a/common/account_utils/src/validator_definitions.rs b/common/account_utils/src/validator_definitions.rs index c91e717d11b..8dc0888e6ed 100644 --- a/common/account_utils/src/validator_definitions.rs +++ b/common/account_utils/src/validator_definitions.rs @@ -9,7 +9,7 @@ use crate::{ use directory::ensure_dir_exists; use eth2_keystore::Keystore; use regex::Regex; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use slog::{error, Logger}; use std::collections::HashSet; use std::fs::{self, File}; diff --git a/common/eth2_interop_keypairs/Cargo.toml b/common/eth2_interop_keypairs/Cargo.toml index ded62653e56..6f92acc84a0 100644 --- a/common/eth2_interop_keypairs/Cargo.toml +++ b/common/eth2_interop_keypairs/Cargo.toml @@ -13,7 +13,6 @@ ethereum_hashing = { workspace = true } hex = { workspace = true } serde_yaml = { workspace = true } serde = { workspace = true } -serde_derive = "1.0.116" bls = { workspace = true } [dev-dependencies] diff --git a/common/eth2_interop_keypairs/src/lib.rs b/common/eth2_interop_keypairs/src/lib.rs index 7b5fa7a8e4f..3d4ff02c383 100644 --- a/common/eth2_interop_keypairs/src/lib.rs +++ b/common/eth2_interop_keypairs/src/lib.rs @@ -22,7 +22,7 @@ extern crate lazy_static; use bls::{Keypair, PublicKey, SecretKey}; use ethereum_hashing::hash; use num_bigint::BigUint; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::convert::TryInto; use std::fs::File; use std::path::PathBuf; diff --git a/common/monitoring_api/Cargo.toml b/common/monitoring_api/Cargo.toml index e22f747bb1a..3731229c393 100644 --- a/common/monitoring_api/Cargo.toml +++ b/common/monitoring_api/Cargo.toml @@ -12,7 +12,6 @@ task_executor = { workspace = true } tokio = { workspace = true } eth2 = { workspace = true } serde_json = { workspace = true } -serde_derive = "1.0.116" serde = { workspace = true } lighthouse_version = { workspace = true } lighthouse_metrics = { workspace = true } diff --git a/common/monitoring_api/src/types.rs b/common/monitoring_api/src/types.rs index 9765e34613f..cf33ccb9c04 100644 --- a/common/monitoring_api/src/types.rs +++ b/common/monitoring_api/src/types.rs @@ -1,7 +1,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use eth2::lighthouse::{ProcessHealth, SystemHealth}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; pub const VERSION: u64 = 1; pub const CLIENT_NAME: &str = "lighthouse"; diff --git a/common/system_health/Cargo.toml b/common/system_health/Cargo.toml index c02380c9d4a..5f0de80d90e 100644 --- a/common/system_health/Cargo.toml +++ b/common/system_health/Cargo.toml @@ -8,6 +8,5 @@ lighthouse_network = { workspace = true } types = { workspace = true } sysinfo = { workspace = true } serde = { workspace = true } -serde_derive = "1.0.116" serde_json = { workspace = true } parking_lot = { workspace = true } diff --git a/consensus/proto_array/Cargo.toml b/consensus/proto_array/Cargo.toml index b30173eb7e0..99f98cf545f 100644 --- a/consensus/proto_array/Cargo.toml +++ b/consensus/proto_array/Cargo.toml @@ -13,7 +13,6 @@ types = { workspace = true } ethereum_ssz = { workspace = true } ethereum_ssz_derive = { workspace = true } serde = { workspace = true } -serde_derive = "1.0.116" serde_yaml = { workspace = true } safe_arith = { workspace = true } -superstruct = { workspace = true } \ No newline at end of file +superstruct = { workspace = true } diff --git a/consensus/proto_array/src/fork_choice_test_definition.rs b/consensus/proto_array/src/fork_choice_test_definition.rs index 98d43e4850c..ebb639819d2 100644 --- a/consensus/proto_array/src/fork_choice_test_definition.rs +++ b/consensus/proto_array/src/fork_choice_test_definition.rs @@ -5,7 +5,7 @@ mod votes; use crate::proto_array_fork_choice::{Block, ExecutionStatus, ProtoArrayForkChoice}; use crate::{InvalidationOperation, JustifiedBalances}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::collections::BTreeSet; use types::{ AttestationShufflingId, Checkpoint, Epoch, EthSpec, ExecutionBlockHash, Hash256, diff --git a/consensus/proto_array/src/proto_array.rs b/consensus/proto_array/src/proto_array.rs index 7b6afb94f54..6b88e5e4269 100644 --- a/consensus/proto_array/src/proto_array.rs +++ b/consensus/proto_array/src/proto_array.rs @@ -1,6 +1,6 @@ use crate::error::InvalidBestNodeInfo; use crate::{error::Error, Block, ExecutionStatus, JustifiedBalances}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz::four_byte_option_impl; use ssz::Encode; use ssz_derive::{Decode, Encode}; diff --git a/consensus/proto_array/src/proto_array_fork_choice.rs b/consensus/proto_array/src/proto_array_fork_choice.rs index 5911e50fcdc..6fc677073ed 100644 --- a/consensus/proto_array/src/proto_array_fork_choice.rs +++ b/consensus/proto_array/src/proto_array_fork_choice.rs @@ -7,7 +7,7 @@ use crate::{ ssz_container::SszContainer, JustifiedBalances, }; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use std::{ diff --git a/consensus/types/Cargo.toml b/consensus/types/Cargo.toml index e092ff02acf..82fd1ef7aa3 100644 --- a/consensus/types/Cargo.toml +++ b/consensus/types/Cargo.toml @@ -24,7 +24,6 @@ rayon = { workspace = true } rand = { workspace = true } safe_arith = { workspace = true } serde = { workspace = true, features = ["rc"] } -serde_derive = "1.0.116" slog = { workspace = true } ethereum_ssz = { workspace = true, features = ["arbitrary"] } ethereum_ssz_derive = { workspace = true } diff --git a/consensus/types/src/aggregate_and_proof.rs b/consensus/types/src/aggregate_and_proof.rs index 20d66cd4471..ac31e78cb73 100644 --- a/consensus/types/src/aggregate_and_proof.rs +++ b/consensus/types/src/aggregate_and_proof.rs @@ -3,7 +3,7 @@ use super::{ Signature, SignedRoot, }; use crate::test_utils::TestRandom; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/attestation.rs b/consensus/types/src/attestation.rs index 5c333e0d456..ac4a583cbb6 100644 --- a/consensus/types/src/attestation.rs +++ b/consensus/types/src/attestation.rs @@ -1,6 +1,6 @@ use derivative::Derivative; use safe_arith::ArithError; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/attestation_data.rs b/consensus/types/src/attestation_data.rs index 286502b4497..7578981f514 100644 --- a/consensus/types/src/attestation_data.rs +++ b/consensus/types/src/attestation_data.rs @@ -2,7 +2,7 @@ use crate::test_utils::TestRandom; use crate::{Checkpoint, Hash256, SignedRoot, Slot}; use crate::slot_data::SlotData; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/attestation_duty.rs b/consensus/types/src/attestation_duty.rs index 93a4c147b67..22b03dda61f 100644 --- a/consensus/types/src/attestation_duty.rs +++ b/consensus/types/src/attestation_duty.rs @@ -1,5 +1,5 @@ use crate::*; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; #[derive(arbitrary::Arbitrary, Debug, PartialEq, Clone, Copy, Default, Serialize, Deserialize)] pub struct AttestationDuty { diff --git a/consensus/types/src/attester_slashing.rs b/consensus/types/src/attester_slashing.rs index c5634950745..c2bbea637e8 100644 --- a/consensus/types/src/attester_slashing.rs +++ b/consensus/types/src/attester_slashing.rs @@ -1,7 +1,7 @@ use crate::{test_utils::TestRandom, EthSpec, IndexedAttestation}; use derivative::Derivative; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/beacon_block.rs b/consensus/types/src/beacon_block.rs index 0d52d708012..7215c779e73 100644 --- a/consensus/types/src/beacon_block.rs +++ b/consensus/types/src/beacon_block.rs @@ -6,7 +6,7 @@ use crate::test_utils::TestRandom; use crate::*; use bls::Signature; use derivative::Derivative; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz::{Decode, DecodeError}; use ssz_derive::{Decode, Encode}; use std::marker::PhantomData; diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index d5a220a5d9f..ba609387409 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -1,7 +1,7 @@ use crate::test_utils::TestRandom; use crate::*; use derivative::Derivative; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use ssz_types::VariableList; use std::marker::PhantomData; diff --git a/consensus/types/src/beacon_block_header.rs b/consensus/types/src/beacon_block_header.rs index f2ef0a3dccd..689f1a28b08 100644 --- a/consensus/types/src/beacon_block_header.rs +++ b/consensus/types/src/beacon_block_header.rs @@ -1,7 +1,7 @@ use crate::test_utils::TestRandom; use crate::*; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash::TreeHash; diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 82a5d305d1e..18b9866f35d 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -9,7 +9,7 @@ use ethereum_hashing::hash; use int_to_bytes::{int_to_bytes4, int_to_bytes8}; use pubkey_cache::PubkeyCache; use safe_arith::{ArithError, SafeArith}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz::{ssz_encode, Decode, DecodeError, Encode}; use ssz_derive::{Decode, Encode}; use ssz_types::{typenum::Unsigned, BitVector, FixedVector}; diff --git a/consensus/types/src/beacon_state/committee_cache.rs b/consensus/types/src/beacon_state/committee_cache.rs index 64bf686f34e..8d29bc22171 100644 --- a/consensus/types/src/beacon_state/committee_cache.rs +++ b/consensus/types/src/beacon_state/committee_cache.rs @@ -4,7 +4,7 @@ use super::BeaconState; use crate::*; use core::num::NonZeroUsize; use safe_arith::SafeArith; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz::{four_byte_option_impl, Decode, DecodeError, Encode}; use ssz_derive::{Decode, Encode}; use std::ops::Range; diff --git a/consensus/types/src/beacon_state/exit_cache.rs b/consensus/types/src/beacon_state/exit_cache.rs index b657d62ae62..cb96fba6917 100644 --- a/consensus/types/src/beacon_state/exit_cache.rs +++ b/consensus/types/src/beacon_state/exit_cache.rs @@ -1,6 +1,6 @@ use super::{BeaconStateError, ChainSpec, Epoch, Validator}; use safe_arith::SafeArith; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; /// Map from exit epoch to the number of validators with that exit epoch. diff --git a/consensus/types/src/beacon_state/progressive_balances_cache.rs b/consensus/types/src/beacon_state/progressive_balances_cache.rs index 3e776965cb8..6c0682480bf 100644 --- a/consensus/types/src/beacon_state/progressive_balances_cache.rs +++ b/consensus/types/src/beacon_state/progressive_balances_cache.rs @@ -2,7 +2,7 @@ use crate::beacon_state::balance::Balance; use crate::{BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec}; use arbitrary::Arbitrary; use safe_arith::SafeArith; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use strum::{Display, EnumString, EnumVariantNames}; /// This cache keeps track of the accumulated target attestation balance for the current & previous diff --git a/consensus/types/src/beacon_state/pubkey_cache.rs b/consensus/types/src/beacon_state/pubkey_cache.rs index 590ea30f999..c56c9077e1a 100644 --- a/consensus/types/src/beacon_state/pubkey_cache.rs +++ b/consensus/types/src/beacon_state/pubkey_cache.rs @@ -1,5 +1,5 @@ use crate::*; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; type ValidatorIndex = usize; diff --git a/consensus/types/src/blob_sidecar.rs b/consensus/types/src/blob_sidecar.rs index e4da89ca2f5..a5fbc3206a1 100644 --- a/consensus/types/src/blob_sidecar.rs +++ b/consensus/types/src/blob_sidecar.rs @@ -3,7 +3,7 @@ use crate::{Blob, EthSpec, Hash256, SignedRoot, Slot}; use derivative::Derivative; use kzg::{Kzg, KzgCommitment, KzgPreset, KzgProof, BYTES_PER_FIELD_ELEMENT}; use rand::Rng; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz::Encode; use ssz_derive::{Decode, Encode}; use ssz_types::{FixedVector, VariableList}; diff --git a/consensus/types/src/bls_to_execution_change.rs b/consensus/types/src/bls_to_execution_change.rs index 3ed9ee9255e..baa65f5172d 100644 --- a/consensus/types/src/bls_to_execution_change.rs +++ b/consensus/types/src/bls_to_execution_change.rs @@ -1,7 +1,7 @@ use crate::test_utils::TestRandom; use crate::*; use bls::PublicKeyBytes; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/builder_bid.rs b/consensus/types/src/builder_bid.rs index d621c389144..910ef97c71c 100644 --- a/consensus/types/src/builder_bid.rs +++ b/consensus/types/src/builder_bid.rs @@ -6,8 +6,7 @@ use crate::{ }; use bls::PublicKeyBytes; use bls::Signature; -use serde::Deserializer; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; use ssz_derive::Encode; use superstruct::superstruct; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/chain_spec.rs b/consensus/types/src/chain_spec.rs index 6562dc00ae5..680db57c227 100644 --- a/consensus/types/src/chain_spec.rs +++ b/consensus/types/src/chain_spec.rs @@ -1,8 +1,8 @@ use crate::application_domain::{ApplicationDomain, APPLICATION_DOMAIN_BUILDER}; use crate::*; use int_to_bytes::int_to_bytes4; +use serde::Deserialize; use serde::{Deserializer, Serialize, Serializer}; -use serde_derive::Deserialize; use serde_utils::quoted_u64::MaybeQuoted; use std::fs::File; use std::path::Path; diff --git a/consensus/types/src/checkpoint.rs b/consensus/types/src/checkpoint.rs index e84798f6f7d..044fc57f22a 100644 --- a/consensus/types/src/checkpoint.rs +++ b/consensus/types/src/checkpoint.rs @@ -1,6 +1,6 @@ use crate::test_utils::TestRandom; use crate::{Epoch, Hash256}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/config_and_preset.rs b/consensus/types/src/config_and_preset.rs index 911aa585d72..bd2efd3d9ee 100644 --- a/consensus/types/src/config_and_preset.rs +++ b/consensus/types/src/config_and_preset.rs @@ -3,7 +3,7 @@ use crate::{ DenebPreset, EthSpec, ForkName, }; use maplit::hashmap; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; use superstruct::superstruct; diff --git a/consensus/types/src/contribution_and_proof.rs b/consensus/types/src/contribution_and_proof.rs index 7e757f89b1a..aba98c92b7d 100644 --- a/consensus/types/src/contribution_and_proof.rs +++ b/consensus/types/src/contribution_and_proof.rs @@ -3,7 +3,7 @@ use super::{ SyncSelectionProof, }; use crate::test_utils::TestRandom; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/deposit.rs b/consensus/types/src/deposit.rs index bbc3bd9fb89..c818c7d8081 100644 --- a/consensus/types/src/deposit.rs +++ b/consensus/types/src/deposit.rs @@ -1,6 +1,6 @@ use crate::test_utils::TestRandom; use crate::*; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use ssz_types::typenum::U33; use test_random_derive::TestRandom; diff --git a/consensus/types/src/deposit_data.rs b/consensus/types/src/deposit_data.rs index d75643f6597..e074ffdfaa1 100644 --- a/consensus/types/src/deposit_data.rs +++ b/consensus/types/src/deposit_data.rs @@ -2,7 +2,7 @@ use crate::test_utils::TestRandom; use crate::*; use bls::{PublicKeyBytes, SignatureBytes}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/deposit_message.rs b/consensus/types/src/deposit_message.rs index 1096cfaa283..e5c666df822 100644 --- a/consensus/types/src/deposit_message.rs +++ b/consensus/types/src/deposit_message.rs @@ -2,7 +2,7 @@ use crate::test_utils::TestRandom; use crate::*; use bls::PublicKeyBytes; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/deposit_tree_snapshot.rs b/consensus/types/src/deposit_tree_snapshot.rs index 12e81d0028f..d4dcdb2edaa 100644 --- a/consensus/types/src/deposit_tree_snapshot.rs +++ b/consensus/types/src/deposit_tree_snapshot.rs @@ -1,7 +1,7 @@ use crate::*; use ethereum_hashing::{hash32_concat, ZERO_HASHES}; use int_to_bytes::int_to_bytes32; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use test_utils::TestRandom; diff --git a/consensus/types/src/enr_fork_id.rs b/consensus/types/src/enr_fork_id.rs index 409383c9048..3ae7c39cfe9 100644 --- a/consensus/types/src/enr_fork_id.rs +++ b/consensus/types/src/enr_fork_id.rs @@ -1,7 +1,7 @@ use crate::test_utils::TestRandom; use crate::Epoch; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/eth1_data.rs b/consensus/types/src/eth1_data.rs index d8f476b99b5..e2c4e511ef3 100644 --- a/consensus/types/src/eth1_data.rs +++ b/consensus/types/src/eth1_data.rs @@ -1,7 +1,7 @@ use super::Hash256; use crate::test_utils::TestRandom; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/eth_spec.rs b/consensus/types/src/eth_spec.rs index cde438e9e32..3ad6781941b 100644 --- a/consensus/types/src/eth_spec.rs +++ b/consensus/types/src/eth_spec.rs @@ -2,7 +2,7 @@ use crate::*; use kzg::{BlobTrait, KzgPreset, MainnetKzgPreset, MinimalKzgPreset}; use safe_arith::SafeArith; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_types::typenum::{ bit::B0, UInt, Unsigned, U0, U1024, U1048576, U1073741824, U1099511627776, U128, U131072, U16, U16777216, U2, U2048, U256, U32, U4, U4096, U512, U6, U625, U64, U65536, U8, U8192, diff --git a/consensus/types/src/execution_block_hash.rs b/consensus/types/src/execution_block_hash.rs index 8c4d677517d..b2401f0c0f1 100644 --- a/consensus/types/src/execution_block_hash.rs +++ b/consensus/types/src/execution_block_hash.rs @@ -2,7 +2,7 @@ use crate::test_utils::TestRandom; use crate::Hash256; use derivative::Derivative; use rand::RngCore; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz::{Decode, DecodeError, Encode}; use std::fmt; diff --git a/consensus/types/src/execution_payload.rs b/consensus/types/src/execution_payload.rs index 41241b21bee..1dc5951b253 100644 --- a/consensus/types/src/execution_payload.rs +++ b/consensus/types/src/execution_payload.rs @@ -1,6 +1,6 @@ use crate::{test_utils::TestRandom, *}; use derivative::Derivative; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; diff --git a/consensus/types/src/execution_payload_header.rs b/consensus/types/src/execution_payload_header.rs index ba2260eae55..e0859c0a1e9 100644 --- a/consensus/types/src/execution_payload_header.rs +++ b/consensus/types/src/execution_payload_header.rs @@ -1,6 +1,6 @@ use crate::{test_utils::TestRandom, *}; use derivative::Derivative; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz::Decode; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; diff --git a/consensus/types/src/fork.rs b/consensus/types/src/fork.rs index 4650881f72d..b23113f4363 100644 --- a/consensus/types/src/fork.rs +++ b/consensus/types/src/fork.rs @@ -1,7 +1,7 @@ use crate::test_utils::TestRandom; use crate::Epoch; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/fork_data.rs b/consensus/types/src/fork_data.rs index bf9c48cd7eb..52ce57a2a94 100644 --- a/consensus/types/src/fork_data.rs +++ b/consensus/types/src/fork_data.rs @@ -1,7 +1,7 @@ use crate::test_utils::TestRandom; use crate::{Hash256, SignedRoot}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/fork_name.rs b/consensus/types/src/fork_name.rs index e87b6d61ae1..6523b2a678c 100644 --- a/consensus/types/src/fork_name.rs +++ b/consensus/types/src/fork_name.rs @@ -1,5 +1,5 @@ use crate::{ChainSpec, Epoch}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use std::convert::TryFrom; use std::fmt::{self, Display, Formatter}; diff --git a/consensus/types/src/historical_batch.rs b/consensus/types/src/historical_batch.rs index e75b64cae93..e3e037fd630 100644 --- a/consensus/types/src/historical_batch.rs +++ b/consensus/types/src/historical_batch.rs @@ -1,7 +1,7 @@ use crate::test_utils::TestRandom; use crate::*; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use ssz_types::FixedVector; use test_random_derive::TestRandom; diff --git a/consensus/types/src/historical_summary.rs b/consensus/types/src/historical_summary.rs index 84d87b85fd9..dcc387d3d6f 100644 --- a/consensus/types/src/historical_summary.rs +++ b/consensus/types/src/historical_summary.rs @@ -4,7 +4,7 @@ use crate::{BeaconState, EthSpec, Hash256}; use cached_tree_hash::Error; use cached_tree_hash::{int_log, CacheArena, CachedTreeHash, TreeHashCache}; use compare_fields_derive::CompareFields; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use ssz_types::VariableList; use test_random_derive::TestRandom; diff --git a/consensus/types/src/indexed_attestation.rs b/consensus/types/src/indexed_attestation.rs index c59cbef307e..c2d48d72428 100644 --- a/consensus/types/src/indexed_attestation.rs +++ b/consensus/types/src/indexed_attestation.rs @@ -1,6 +1,6 @@ use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, EthSpec, VariableList}; use derivative::Derivative; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz::Encode; use ssz_derive::{Decode, Encode}; use std::hash::{Hash, Hasher}; diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 1a5eed2205d..1d70456d732 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -1,6 +1,6 @@ use super::{BeaconBlockHeader, BeaconState, EthSpec, FixedVector, Hash256, SyncCommittee}; use crate::{light_client_update::*, test_utils::TestRandom}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use std::sync::Arc; use test_random_derive::TestRandom; diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index 08069c93084..30f337fb2bb 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -3,7 +3,7 @@ use super::{ Slot, SyncAggregate, }; use crate::{light_client_update::*, test_utils::TestRandom, BeaconState, ChainSpec}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash::TreeHash; diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index 7a39bd9ac1c..fbb0558eced 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -2,7 +2,7 @@ use super::{BeaconBlockHeader, EthSpec, Slot, SyncAggregate}; use crate::{ light_client_update::Error, test_utils::TestRandom, BeaconState, ChainSpec, SignedBeaconBlock, }; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash::TreeHash; diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index ca35f96802b..6e53e14c994 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -1,7 +1,7 @@ use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee}; use crate::{beacon_state, test_utils::TestRandom, BeaconBlock, BeaconState, ChainSpec}; use safe_arith::ArithError; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use ssz_types::typenum::{U5, U6}; use std::sync::Arc; diff --git a/consensus/types/src/participation_flags.rs b/consensus/types/src/participation_flags.rs index 4f170a60be8..e94e56f0cde 100644 --- a/consensus/types/src/participation_flags.rs +++ b/consensus/types/src/participation_flags.rs @@ -1,6 +1,6 @@ use crate::{consts::altair::NUM_FLAG_INDICES, test_utils::TestRandom, Hash256}; use safe_arith::{ArithError, SafeArith}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz::{Decode, DecodeError, Encode}; use test_random_derive::TestRandom; use tree_hash::{PackedEncoding, TreeHash, TreeHashType}; diff --git a/consensus/types/src/pending_attestation.rs b/consensus/types/src/pending_attestation.rs index 88db0ec4d33..d25a6987c0b 100644 --- a/consensus/types/src/pending_attestation.rs +++ b/consensus/types/src/pending_attestation.rs @@ -1,7 +1,7 @@ use crate::test_utils::TestRandom; use crate::{AttestationData, BitList, EthSpec}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/preset.rs b/consensus/types/src/preset.rs index a1c1e7024ca..63a372ea1c9 100644 --- a/consensus/types/src/preset.rs +++ b/consensus/types/src/preset.rs @@ -1,5 +1,5 @@ use crate::{ChainSpec, Epoch, EthSpec, Unsigned}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; /// Value-level representation of an Ethereum consensus "preset". /// diff --git a/consensus/types/src/proposer_slashing.rs b/consensus/types/src/proposer_slashing.rs index 1ac2464a47f..ee55d62c201 100644 --- a/consensus/types/src/proposer_slashing.rs +++ b/consensus/types/src/proposer_slashing.rs @@ -1,7 +1,7 @@ use crate::test_utils::TestRandom; use crate::SignedBeaconBlockHeader; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/shuffling_id.rs b/consensus/types/src/shuffling_id.rs index 120d744a5ec..a5bdc866733 100644 --- a/consensus/types/src/shuffling_id.rs +++ b/consensus/types/src/shuffling_id.rs @@ -1,5 +1,5 @@ use crate::*; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use std::hash::Hash; diff --git a/consensus/types/src/signed_aggregate_and_proof.rs b/consensus/types/src/signed_aggregate_and_proof.rs index 6d86c056349..10010073e54 100644 --- a/consensus/types/src/signed_aggregate_and_proof.rs +++ b/consensus/types/src/signed_aggregate_and_proof.rs @@ -3,7 +3,7 @@ use super::{ SelectionProof, Signature, SignedRoot, }; use crate::test_utils::TestRandom; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/signed_beacon_block.rs b/consensus/types/src/signed_beacon_block.rs index 2234e38f083..11009457fda 100644 --- a/consensus/types/src/signed_beacon_block.rs +++ b/consensus/types/src/signed_beacon_block.rs @@ -1,7 +1,7 @@ use crate::*; use bls::Signature; use derivative::Derivative; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use std::fmt; use superstruct::superstruct; diff --git a/consensus/types/src/signed_beacon_block_header.rs b/consensus/types/src/signed_beacon_block_header.rs index c265eded1d5..3d4269a2cef 100644 --- a/consensus/types/src/signed_beacon_block_header.rs +++ b/consensus/types/src/signed_beacon_block_header.rs @@ -2,7 +2,7 @@ use crate::{ test_utils::TestRandom, BeaconBlockHeader, ChainSpec, Domain, EthSpec, Fork, Hash256, PublicKey, Signature, SignedRoot, }; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/signed_blob.rs b/consensus/types/src/signed_blob.rs index b8fab8e1220..3c560823cea 100644 --- a/consensus/types/src/signed_blob.rs +++ b/consensus/types/src/signed_blob.rs @@ -5,7 +5,7 @@ use crate::{ }; use bls::PublicKey; use derivative::Derivative; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use ssz_types::VariableList; use std::marker::PhantomData; diff --git a/consensus/types/src/signed_bls_to_execution_change.rs b/consensus/types/src/signed_bls_to_execution_change.rs index 2b17095ae7d..2a4ecdf4387 100644 --- a/consensus/types/src/signed_bls_to_execution_change.rs +++ b/consensus/types/src/signed_bls_to_execution_change.rs @@ -1,7 +1,7 @@ use crate::test_utils::TestRandom; use crate::*; use bls::Signature; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/signed_contribution_and_proof.rs b/consensus/types/src/signed_contribution_and_proof.rs index 4cb35884338..6cb45ac8e6b 100644 --- a/consensus/types/src/signed_contribution_and_proof.rs +++ b/consensus/types/src/signed_contribution_and_proof.rs @@ -3,7 +3,7 @@ use super::{ SignedRoot, SyncCommitteeContribution, SyncSelectionProof, }; use crate::test_utils::TestRandom; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/signed_voluntary_exit.rs b/consensus/types/src/signed_voluntary_exit.rs index 3392826a62f..30eda117919 100644 --- a/consensus/types/src/signed_voluntary_exit.rs +++ b/consensus/types/src/signed_voluntary_exit.rs @@ -1,7 +1,7 @@ use crate::{test_utils::TestRandom, VoluntaryExit}; use bls::Signature; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/signing_data.rs b/consensus/types/src/signing_data.rs index b80d4a40d5a..f30d5fdfcb4 100644 --- a/consensus/types/src/signing_data.rs +++ b/consensus/types/src/signing_data.rs @@ -1,7 +1,7 @@ use crate::test_utils::TestRandom; use crate::Hash256; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash::TreeHash; diff --git a/consensus/types/src/slot_epoch.rs b/consensus/types/src/slot_epoch.rs index 991261d16ad..ec659d1dbbf 100644 --- a/consensus/types/src/slot_epoch.rs +++ b/consensus/types/src/slot_epoch.rs @@ -15,7 +15,7 @@ use crate::{ChainSpec, SignedRoot}; use rand::RngCore; use safe_arith::{ArithError, SafeArith}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz::{Decode, DecodeError, Encode}; use std::fmt; use std::hash::Hash; diff --git a/consensus/types/src/subnet_id.rs b/consensus/types/src/subnet_id.rs index 415d6a14040..2752e31092e 100644 --- a/consensus/types/src/subnet_id.rs +++ b/consensus/types/src/subnet_id.rs @@ -1,7 +1,7 @@ //! Identifies each shard by an integer identifier. use crate::{AttestationData, ChainSpec, CommitteeIndex, Epoch, EthSpec, Slot}; use safe_arith::{ArithError, SafeArith}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::ops::{Deref, DerefMut}; use swap_or_not_shuffle::compute_shuffled_index; diff --git a/consensus/types/src/sync_aggregate.rs b/consensus/types/src/sync_aggregate.rs index 300c86fc0f8..bb00c4aa205 100644 --- a/consensus/types/src/sync_aggregate.rs +++ b/consensus/types/src/sync_aggregate.rs @@ -3,7 +3,7 @@ use crate::test_utils::TestRandom; use crate::{AggregateSignature, BitVector, EthSpec, SyncCommitteeContribution}; use derivative::Derivative; use safe_arith::{ArithError, SafeArith}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/sync_aggregator_selection_data.rs b/consensus/types/src/sync_aggregator_selection_data.rs index b101068123b..2b60d01b8ee 100644 --- a/consensus/types/src/sync_aggregator_selection_data.rs +++ b/consensus/types/src/sync_aggregator_selection_data.rs @@ -1,7 +1,7 @@ use crate::test_utils::TestRandom; use crate::{SignedRoot, Slot}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/sync_committee.rs b/consensus/types/src/sync_committee.rs index 43ba23f121c..0bcf505f257 100644 --- a/consensus/types/src/sync_committee.rs +++ b/consensus/types/src/sync_committee.rs @@ -3,7 +3,7 @@ use crate::typenum::Unsigned; use crate::{EthSpec, FixedVector, SyncSubnetId}; use bls::PublicKeyBytes; use safe_arith::{ArithError, SafeArith}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use std::collections::HashMap; use test_random_derive::TestRandom; diff --git a/consensus/types/src/sync_committee_contribution.rs b/consensus/types/src/sync_committee_contribution.rs index 425f8f116d4..b8ee5c2e365 100644 --- a/consensus/types/src/sync_committee_contribution.rs +++ b/consensus/types/src/sync_committee_contribution.rs @@ -2,7 +2,7 @@ use super::{AggregateSignature, EthSpec, SignedRoot}; use crate::slot_data::SlotData; use crate::{test_utils::TestRandom, BitVector, Hash256, Slot, SyncCommitteeMessage}; use safe_arith::ArithError; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/sync_committee_message.rs b/consensus/types/src/sync_committee_message.rs index d0301cdf638..d7d309cd567 100644 --- a/consensus/types/src/sync_committee_message.rs +++ b/consensus/types/src/sync_committee_message.rs @@ -2,7 +2,7 @@ use crate::test_utils::TestRandom; use crate::{ChainSpec, Domain, EthSpec, Fork, Hash256, SecretKey, Signature, SignedRoot, Slot}; use crate::slot_data::SlotData; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/sync_duty.rs b/consensus/types/src/sync_duty.rs index e3ffe62bfd1..1058b9d3b4f 100644 --- a/consensus/types/src/sync_duty.rs +++ b/consensus/types/src/sync_duty.rs @@ -1,7 +1,7 @@ use crate::{EthSpec, SyncCommittee, SyncSubnetId}; use bls::PublicKeyBytes; use safe_arith::ArithError; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::collections::HashSet; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/consensus/types/src/sync_subnet_id.rs b/consensus/types/src/sync_subnet_id.rs index 5af756ae013..56054829292 100644 --- a/consensus/types/src/sync_subnet_id.rs +++ b/consensus/types/src/sync_subnet_id.rs @@ -2,7 +2,7 @@ use crate::consts::altair::SYNC_COMMITTEE_SUBNET_COUNT; use crate::EthSpec; use safe_arith::{ArithError, SafeArith}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_types::typenum::Unsigned; use std::collections::HashSet; use std::fmt::{self, Display}; diff --git a/consensus/types/src/validator.rs b/consensus/types/src/validator.rs index 6860397fb5b..8fbd9009ea5 100644 --- a/consensus/types/src/validator.rs +++ b/consensus/types/src/validator.rs @@ -2,7 +2,7 @@ use crate::{ test_utils::TestRandom, Address, BeaconState, ChainSpec, Epoch, EthSpec, Hash256, PublicKeyBytes, }; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/voluntary_exit.rs b/consensus/types/src/voluntary_exit.rs index 446029b560b..a24f7376a1b 100644 --- a/consensus/types/src/voluntary_exit.rs +++ b/consensus/types/src/voluntary_exit.rs @@ -3,7 +3,7 @@ use crate::{ SignedVoluntaryExit, }; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/consensus/types/src/withdrawal.rs b/consensus/types/src/withdrawal.rs index eed7c7e277f..3e611565541 100644 --- a/consensus/types/src/withdrawal.rs +++ b/consensus/types/src/withdrawal.rs @@ -1,6 +1,6 @@ use crate::test_utils::TestRandom; use crate::*; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; diff --git a/crypto/bls/Cargo.toml b/crypto/bls/Cargo.toml index 4340fb3e853..1216fc2a986 100644 --- a/crypto/bls/Cargo.toml +++ b/crypto/bls/Cargo.toml @@ -10,7 +10,6 @@ tree_hash = { workspace = true } milagro_bls = { git = "https://github.com/sigp/milagro_bls", tag = "v1.5.1", optional = true } rand = { workspace = true } serde = { workspace = true } -serde_derive = "1.0.116" ethereum_serde_utils = { workspace = true } hex = { workspace = true } ethereum_hashing = { workspace = true } diff --git a/crypto/bls/src/zeroize_hash.rs b/crypto/bls/src/zeroize_hash.rs index 41136f97a7b..e346f456d1d 100644 --- a/crypto/bls/src/zeroize_hash.rs +++ b/crypto/bls/src/zeroize_hash.rs @@ -1,5 +1,5 @@ use super::SECRET_KEY_BYTES_LEN; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use zeroize::Zeroize; /// Provides a wrapper around a `[u8; SECRET_KEY_BYTES_LEN]` that implements `Zeroize` on `Drop`. diff --git a/crypto/kzg/src/lib.rs b/crypto/kzg/src/lib.rs index d7870c15bb1..410ae8a4951 100644 --- a/crypto/kzg/src/lib.rs +++ b/crypto/kzg/src/lib.rs @@ -2,7 +2,7 @@ mod kzg_commitment; mod kzg_proof; mod trusted_setup; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::fmt::Debug; use std::ops::Deref; use std::str::FromStr; diff --git a/lighthouse/environment/Cargo.toml b/lighthouse/environment/Cargo.toml index d2a181a1b9f..b57e1e9dee0 100644 --- a/lighthouse/environment/Cargo.toml +++ b/lighthouse/environment/Cargo.toml @@ -19,7 +19,6 @@ futures = { workspace = true } slog-json = "2.3.0" exit-future = { workspace = true } serde = { workspace = true } -serde_derive = "1.0.116" [target.'cfg(not(target_family = "unix"))'.dependencies] ctrlc = { version = "3.1.6", features = ["termination"] } diff --git a/lighthouse/environment/src/lib.rs b/lighthouse/environment/src/lib.rs index fc7ab8d52c5..8e7c237a367 100644 --- a/lighthouse/environment/src/lib.rs +++ b/lighthouse/environment/src/lib.rs @@ -13,7 +13,7 @@ use futures::channel::mpsc::{channel, Receiver, Sender}; use futures::{future, StreamExt}; use logging::SSELoggingComponents; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use slog::{error, info, o, warn, Drain, Duplicate, Level, Logger}; use sloggers::{file::FileLoggerBuilder, types::Format, types::Severity, Build}; use std::fs::create_dir_all; diff --git a/slasher/Cargo.toml b/slasher/Cargo.toml index 9df77daa103..87e77494b1e 100644 --- a/slasher/Cargo.toml +++ b/slasher/Cargo.toml @@ -23,7 +23,6 @@ parking_lot = { workspace = true } rand = { workspace = true } safe_arith = { workspace = true } serde = { workspace = true } -serde_derive = "1.0" slog = { workspace = true } sloggers = { workspace = true } tree_hash = { workspace = true } diff --git a/slasher/src/array.rs b/slasher/src/array.rs index 4deb389124a..f3b11cccd7e 100644 --- a/slasher/src/array.rs +++ b/slasher/src/array.rs @@ -4,7 +4,7 @@ use crate::{ SlasherDB, }; use flate2::bufread::{ZlibDecoder, ZlibEncoder}; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::borrow::Borrow; use std::collections::{btree_map::Entry, BTreeMap, HashSet}; use std::convert::TryFrom; diff --git a/slasher/src/config.rs b/slasher/src/config.rs index 361621d176b..894760d277d 100644 --- a/slasher/src/config.rs +++ b/slasher/src/config.rs @@ -1,5 +1,5 @@ use crate::Error; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::path::PathBuf; use strum::{Display, EnumString, EnumVariantNames}; use types::{Epoch, EthSpec, IndexedAttestation}; diff --git a/testing/ef_tests/Cargo.toml b/testing/ef_tests/Cargo.toml index b4bd5a50a04..4c2df4d4f77 100644 --- a/testing/ef_tests/Cargo.toml +++ b/testing/ef_tests/Cargo.toml @@ -20,7 +20,6 @@ hex = { workspace = true } kzg = { workspace = true } rayon = { workspace = true } serde = { workspace = true } -serde_derive = "1.0.116" serde_json = { workspace = true } serde_repr = { workspace = true } serde_yaml = { workspace = true } diff --git a/testing/ef_tests/src/cases/bls_aggregate_sigs.rs b/testing/ef_tests/src/cases/bls_aggregate_sigs.rs index 53387ee4d7a..c1085e07022 100644 --- a/testing/ef_tests/src/cases/bls_aggregate_sigs.rs +++ b/testing/ef_tests/src/cases/bls_aggregate_sigs.rs @@ -2,7 +2,7 @@ use super::*; use crate::case_result::compare_result; use crate::impl_bls_load_case; use bls::{AggregateSignature, Signature}; -use serde_derive::Deserialize; +use serde::Deserialize; #[derive(Debug, Clone, Deserialize)] pub struct BlsAggregateSigs { diff --git a/testing/ef_tests/src/cases/bls_aggregate_verify.rs b/testing/ef_tests/src/cases/bls_aggregate_verify.rs index e9539dc15e1..0e006d95c24 100644 --- a/testing/ef_tests/src/cases/bls_aggregate_verify.rs +++ b/testing/ef_tests/src/cases/bls_aggregate_verify.rs @@ -2,7 +2,7 @@ use super::*; use crate::case_result::compare_result; use crate::impl_bls_load_case; use bls::{AggregateSignature, PublicKeyBytes}; -use serde_derive::Deserialize; +use serde::Deserialize; use types::Hash256; #[derive(Debug, Clone, Deserialize)] diff --git a/testing/ef_tests/src/cases/bls_batch_verify.rs b/testing/ef_tests/src/cases/bls_batch_verify.rs index de8721d67d8..703444c9879 100644 --- a/testing/ef_tests/src/cases/bls_batch_verify.rs +++ b/testing/ef_tests/src/cases/bls_batch_verify.rs @@ -2,7 +2,7 @@ use super::*; use crate::case_result::compare_result; use crate::impl_bls_load_case; use bls::{verify_signature_sets, BlsWrappedSignature, PublicKeyBytes, Signature, SignatureSet}; -use serde_derive::Deserialize; +use serde::Deserialize; use std::borrow::Cow; use std::str::FromStr; use types::Hash256; diff --git a/testing/ef_tests/src/cases/bls_eth_aggregate_pubkeys.rs b/testing/ef_tests/src/cases/bls_eth_aggregate_pubkeys.rs index c41fbca393a..8783aa141e9 100644 --- a/testing/ef_tests/src/cases/bls_eth_aggregate_pubkeys.rs +++ b/testing/ef_tests/src/cases/bls_eth_aggregate_pubkeys.rs @@ -2,7 +2,7 @@ use super::*; use crate::case_result::compare_result; use crate::impl_bls_load_case; use bls::{AggregatePublicKey, PublicKeyBytes}; -use serde_derive::Deserialize; +use serde::Deserialize; #[derive(Debug, Clone, Deserialize)] pub struct BlsEthAggregatePubkeys { diff --git a/testing/ef_tests/src/cases/bls_eth_fast_aggregate_verify.rs b/testing/ef_tests/src/cases/bls_eth_fast_aggregate_verify.rs index 80e018459bb..0fb3a026cfb 100644 --- a/testing/ef_tests/src/cases/bls_eth_fast_aggregate_verify.rs +++ b/testing/ef_tests/src/cases/bls_eth_fast_aggregate_verify.rs @@ -2,7 +2,7 @@ use super::*; use crate::case_result::compare_result; use crate::impl_bls_load_case; use bls::{AggregateSignature, PublicKeyBytes}; -use serde_derive::Deserialize; +use serde::Deserialize; use std::convert::TryInto; use types::Hash256; diff --git a/testing/ef_tests/src/cases/bls_fast_aggregate_verify.rs b/testing/ef_tests/src/cases/bls_fast_aggregate_verify.rs index 608995db9d7..dcdc1bd1979 100644 --- a/testing/ef_tests/src/cases/bls_fast_aggregate_verify.rs +++ b/testing/ef_tests/src/cases/bls_fast_aggregate_verify.rs @@ -2,7 +2,7 @@ use super::*; use crate::case_result::compare_result; use crate::impl_bls_load_case; use bls::{AggregateSignature, PublicKeyBytes}; -use serde_derive::Deserialize; +use serde::Deserialize; use std::convert::TryInto; use types::Hash256; diff --git a/testing/ef_tests/src/cases/bls_sign_msg.rs b/testing/ef_tests/src/cases/bls_sign_msg.rs index 53c13b569a8..6479fabe422 100644 --- a/testing/ef_tests/src/cases/bls_sign_msg.rs +++ b/testing/ef_tests/src/cases/bls_sign_msg.rs @@ -2,7 +2,7 @@ use super::*; use crate::case_result::compare_result; use crate::impl_bls_load_case; use bls::SecretKey; -use serde_derive::Deserialize; +use serde::Deserialize; use types::Hash256; #[derive(Debug, Clone, Deserialize)] diff --git a/testing/ef_tests/src/cases/bls_verify_msg.rs b/testing/ef_tests/src/cases/bls_verify_msg.rs index 779b3cf75f7..24b62c5fa1d 100644 --- a/testing/ef_tests/src/cases/bls_verify_msg.rs +++ b/testing/ef_tests/src/cases/bls_verify_msg.rs @@ -2,7 +2,7 @@ use super::*; use crate::case_result::compare_result; use crate::impl_bls_load_case; use bls::{PublicKeyBytes, Signature, SignatureBytes}; -use serde_derive::Deserialize; +use serde::Deserialize; use std::convert::TryInto; use types::Hash256; diff --git a/testing/ef_tests/src/cases/common.rs b/testing/ef_tests/src/cases/common.rs index 68fd990f1fb..2a7c9987583 100644 --- a/testing/ef_tests/src/cases/common.rs +++ b/testing/ef_tests/src/cases/common.rs @@ -1,4 +1,4 @@ -use serde_derive::Deserialize; +use serde::Deserialize; use ssz::Encode; use ssz_derive::{Decode, Encode}; use std::convert::TryFrom; diff --git a/testing/ef_tests/src/cases/epoch_processing.rs b/testing/ef_tests/src/cases/epoch_processing.rs index 95e406f40eb..cf182af2b21 100644 --- a/testing/ef_tests/src/cases/epoch_processing.rs +++ b/testing/ef_tests/src/cases/epoch_processing.rs @@ -4,7 +4,7 @@ use crate::case_result::compare_beacon_state_results_without_caches; use crate::decode::{ssz_decode_state, yaml_decode_file}; use crate::type_name; use crate::type_name::TypeName; -use serde_derive::Deserialize; +use serde::Deserialize; use state_processing::per_epoch_processing::capella::process_historical_summaries_update; use state_processing::per_epoch_processing::effective_balance_updates::process_effective_balance_updates; use state_processing::per_epoch_processing::{ diff --git a/testing/ef_tests/src/cases/fork.rs b/testing/ef_tests/src/cases/fork.rs index d27fbcd6715..bc340fa1cbb 100644 --- a/testing/ef_tests/src/cases/fork.rs +++ b/testing/ef_tests/src/cases/fork.rs @@ -2,7 +2,7 @@ use super::*; use crate::case_result::compare_beacon_state_results_without_caches; use crate::cases::common::previous_fork; use crate::decode::{ssz_decode_state, yaml_decode_file}; -use serde_derive::Deserialize; +use serde::Deserialize; use state_processing::upgrade::{ upgrade_to_altair, upgrade_to_bellatrix, upgrade_to_capella, upgrade_to_deneb, }; diff --git a/testing/ef_tests/src/cases/genesis_initialization.rs b/testing/ef_tests/src/cases/genesis_initialization.rs index dbf6c70b29b..14fe7ef9590 100644 --- a/testing/ef_tests/src/cases/genesis_initialization.rs +++ b/testing/ef_tests/src/cases/genesis_initialization.rs @@ -1,7 +1,7 @@ use super::*; use crate::case_result::compare_beacon_state_results_without_caches; use crate::decode::{ssz_decode_file, ssz_decode_file_with, ssz_decode_state, yaml_decode_file}; -use serde_derive::Deserialize; +use serde::Deserialize; use state_processing::initialize_beacon_state_from_eth1; use std::path::PathBuf; use types::{BeaconState, Deposit, EthSpec, ExecutionPayloadHeader, ForkName, Hash256}; diff --git a/testing/ef_tests/src/cases/genesis_validity.rs b/testing/ef_tests/src/cases/genesis_validity.rs index abdc1ed4a7a..ec89e0f64b8 100644 --- a/testing/ef_tests/src/cases/genesis_validity.rs +++ b/testing/ef_tests/src/cases/genesis_validity.rs @@ -1,6 +1,6 @@ use super::*; use crate::decode::{ssz_decode_state, yaml_decode_file}; -use serde_derive::Deserialize; +use serde::Deserialize; use state_processing::is_valid_genesis_state; use std::path::Path; use types::{BeaconState, EthSpec, ForkName}; diff --git a/testing/ef_tests/src/cases/kzg_blob_to_kzg_commitment.rs b/testing/ef_tests/src/cases/kzg_blob_to_kzg_commitment.rs index ec2f1b1694f..72a6052fead 100644 --- a/testing/ef_tests/src/cases/kzg_blob_to_kzg_commitment.rs +++ b/testing/ef_tests/src/cases/kzg_blob_to_kzg_commitment.rs @@ -2,7 +2,7 @@ use super::*; use crate::case_result::compare_result; use beacon_chain::kzg_utils::blob_to_kzg_commitment; use kzg::KzgCommitment; -use serde_derive::Deserialize; +use serde::Deserialize; use std::marker::PhantomData; #[derive(Debug, Clone, Deserialize)] diff --git a/testing/ef_tests/src/cases/kzg_compute_blob_kzg_proof.rs b/testing/ef_tests/src/cases/kzg_compute_blob_kzg_proof.rs index 59929678d43..2cec8f1fb3d 100644 --- a/testing/ef_tests/src/cases/kzg_compute_blob_kzg_proof.rs +++ b/testing/ef_tests/src/cases/kzg_compute_blob_kzg_proof.rs @@ -2,7 +2,7 @@ use super::*; use crate::case_result::compare_result; use beacon_chain::kzg_utils::compute_blob_kzg_proof; use kzg::KzgProof; -use serde_derive::Deserialize; +use serde::Deserialize; use std::marker::PhantomData; #[derive(Debug, Clone, Deserialize)] diff --git a/testing/ef_tests/src/cases/kzg_compute_kzg_proof.rs b/testing/ef_tests/src/cases/kzg_compute_kzg_proof.rs index 49cd2d0b885..0085b8bd29f 100644 --- a/testing/ef_tests/src/cases/kzg_compute_kzg_proof.rs +++ b/testing/ef_tests/src/cases/kzg_compute_kzg_proof.rs @@ -2,7 +2,7 @@ use super::*; use crate::case_result::compare_result; use beacon_chain::kzg_utils::compute_kzg_proof; use kzg::KzgProof; -use serde_derive::Deserialize; +use serde::Deserialize; use std::marker::PhantomData; use std::str::FromStr; use types::Hash256; diff --git a/testing/ef_tests/src/cases/kzg_verify_blob_kzg_proof.rs b/testing/ef_tests/src/cases/kzg_verify_blob_kzg_proof.rs index a813efdb7fd..2d18c9bdc0b 100644 --- a/testing/ef_tests/src/cases/kzg_verify_blob_kzg_proof.rs +++ b/testing/ef_tests/src/cases/kzg_verify_blob_kzg_proof.rs @@ -3,7 +3,7 @@ use crate::case_result::compare_result; use beacon_chain::kzg_utils::validate_blob; use eth2_network_config::get_trusted_setup; use kzg::{Kzg, KzgCommitment, KzgPreset, KzgProof, TrustedSetup}; -use serde_derive::Deserialize; +use serde::Deserialize; use std::convert::TryInto; use std::marker::PhantomData; use types::Blob; diff --git a/testing/ef_tests/src/cases/kzg_verify_blob_kzg_proof_batch.rs b/testing/ef_tests/src/cases/kzg_verify_blob_kzg_proof_batch.rs index 5e402e8da9d..cc941618a75 100644 --- a/testing/ef_tests/src/cases/kzg_verify_blob_kzg_proof_batch.rs +++ b/testing/ef_tests/src/cases/kzg_verify_blob_kzg_proof_batch.rs @@ -1,7 +1,7 @@ use super::*; use crate::case_result::compare_result; use beacon_chain::kzg_utils::validate_blobs; -use serde_derive::Deserialize; +use serde::Deserialize; use std::marker::PhantomData; #[derive(Debug, Clone, Deserialize)] diff --git a/testing/ef_tests/src/cases/kzg_verify_kzg_proof.rs b/testing/ef_tests/src/cases/kzg_verify_kzg_proof.rs index aafd9990978..cdf0aeb8e38 100644 --- a/testing/ef_tests/src/cases/kzg_verify_kzg_proof.rs +++ b/testing/ef_tests/src/cases/kzg_verify_kzg_proof.rs @@ -1,7 +1,7 @@ use super::*; use crate::case_result::compare_result; use beacon_chain::kzg_utils::verify_kzg_proof; -use serde_derive::Deserialize; +use serde::Deserialize; use std::marker::PhantomData; #[derive(Debug, Clone, Deserialize)] diff --git a/testing/ef_tests/src/cases/merkle_proof_validity.rs b/testing/ef_tests/src/cases/merkle_proof_validity.rs index c180774bb64..13c83a3b1bc 100644 --- a/testing/ef_tests/src/cases/merkle_proof_validity.rs +++ b/testing/ef_tests/src/cases/merkle_proof_validity.rs @@ -1,6 +1,6 @@ use super::*; use crate::decode::{ssz_decode_state, yaml_decode_file}; -use serde_derive::Deserialize; +use serde::Deserialize; use std::path::Path; use tree_hash::Hash256; use types::{BeaconState, EthSpec, ForkName}; diff --git a/testing/ef_tests/src/cases/operations.rs b/testing/ef_tests/src/cases/operations.rs index 4ccd9e8a710..4c02126d41a 100644 --- a/testing/ef_tests/src/cases/operations.rs +++ b/testing/ef_tests/src/cases/operations.rs @@ -3,7 +3,7 @@ use crate::bls_setting::BlsSetting; use crate::case_result::compare_beacon_state_results_without_caches; use crate::decode::{ssz_decode_file, ssz_decode_file_with, ssz_decode_state, yaml_decode_file}; use crate::testing_spec; -use serde_derive::Deserialize; +use serde::Deserialize; use ssz::Decode; use state_processing::common::update_progressive_balances_cache::initialize_progressive_balances_cache; use state_processing::{ diff --git a/testing/ef_tests/src/cases/rewards.rs b/testing/ef_tests/src/cases/rewards.rs index ee0fc265e11..bb41f6fe12f 100644 --- a/testing/ef_tests/src/cases/rewards.rs +++ b/testing/ef_tests/src/cases/rewards.rs @@ -2,7 +2,7 @@ use super::*; use crate::case_result::compare_result_detailed; use crate::decode::{ssz_decode_file, ssz_decode_state, yaml_decode_file}; use compare_fields_derive::CompareFields; -use serde_derive::Deserialize; +use serde::Deserialize; use ssz::four_byte_option_impl; use ssz_derive::{Decode, Encode}; use state_processing::{ diff --git a/testing/ef_tests/src/cases/sanity_blocks.rs b/testing/ef_tests/src/cases/sanity_blocks.rs index 191b45c33a1..cf8e6b5b2ff 100644 --- a/testing/ef_tests/src/cases/sanity_blocks.rs +++ b/testing/ef_tests/src/cases/sanity_blocks.rs @@ -2,7 +2,7 @@ use super::*; use crate::bls_setting::BlsSetting; use crate::case_result::compare_beacon_state_results_without_caches; use crate::decode::{ssz_decode_file_with, ssz_decode_state, yaml_decode_file}; -use serde_derive::Deserialize; +use serde::Deserialize; use state_processing::{ per_block_processing, per_slot_processing, BlockProcessingError, BlockSignatureStrategy, ConsensusContext, StateProcessingStrategy, VerifyBlockRoot, diff --git a/testing/ef_tests/src/cases/sanity_slots.rs b/testing/ef_tests/src/cases/sanity_slots.rs index dd385d13f4e..0da179d536e 100644 --- a/testing/ef_tests/src/cases/sanity_slots.rs +++ b/testing/ef_tests/src/cases/sanity_slots.rs @@ -2,7 +2,7 @@ use super::*; use crate::bls_setting::BlsSetting; use crate::case_result::compare_beacon_state_results_without_caches; use crate::decode::{ssz_decode_state, yaml_decode_file}; -use serde_derive::Deserialize; +use serde::Deserialize; use state_processing::per_slot_processing; use types::{BeaconState, EthSpec, ForkName}; diff --git a/testing/ef_tests/src/cases/shuffling.rs b/testing/ef_tests/src/cases/shuffling.rs index b5ce019f5ca..e05763c2d86 100644 --- a/testing/ef_tests/src/cases/shuffling.rs +++ b/testing/ef_tests/src/cases/shuffling.rs @@ -1,7 +1,7 @@ use super::*; use crate::case_result::compare_result; use crate::decode::yaml_decode_file; -use serde_derive::Deserialize; +use serde::Deserialize; use std::marker::PhantomData; use swap_or_not_shuffle::{compute_shuffled_index, shuffle_list}; use types::ForkName; diff --git a/testing/ef_tests/src/cases/ssz_generic.rs b/testing/ef_tests/src/cases/ssz_generic.rs index 2374ead8885..d6c764f52b6 100644 --- a/testing/ef_tests/src/cases/ssz_generic.rs +++ b/testing/ef_tests/src/cases/ssz_generic.rs @@ -4,8 +4,8 @@ use super::*; use crate::cases::common::{SszStaticType, TestU128, TestU256}; use crate::cases::ssz_static::{check_serialization, check_tree_hash}; use crate::decode::{snappy_decode_file, yaml_decode_file}; +use serde::Deserialize; use serde::{de::Error as SerdeError, Deserializer}; -use serde_derive::Deserialize; use ssz_derive::{Decode, Encode}; use std::path::{Path, PathBuf}; use tree_hash_derive::TreeHash; diff --git a/testing/ef_tests/src/cases/ssz_static.rs b/testing/ef_tests/src/cases/ssz_static.rs index d0cc5f9eac0..423dc31528f 100644 --- a/testing/ef_tests/src/cases/ssz_static.rs +++ b/testing/ef_tests/src/cases/ssz_static.rs @@ -2,7 +2,7 @@ use super::*; use crate::case_result::compare_result; use crate::cases::common::SszStaticType; use crate::decode::{snappy_decode_file, yaml_decode_file}; -use serde_derive::Deserialize; +use serde::Deserialize; use ssz::Decode; use tree_hash::TreeHash; use types::{BeaconBlock, BeaconState, ForkName, Hash256, SignedBeaconBlock}; diff --git a/testing/ef_tests/src/cases/transition.rs b/testing/ef_tests/src/cases/transition.rs index 5c6da900e61..c94ce3a23a0 100644 --- a/testing/ef_tests/src/cases/transition.rs +++ b/testing/ef_tests/src/cases/transition.rs @@ -1,7 +1,7 @@ use super::*; use crate::case_result::compare_beacon_state_results_without_caches; use crate::decode::{ssz_decode_file_with, ssz_decode_state, yaml_decode_file}; -use serde_derive::Deserialize; +use serde::Deserialize; use state_processing::{ per_block_processing, state_advance::complete_state_advance, BlockSignatureStrategy, ConsensusContext, StateProcessingStrategy, VerifyBlockRoot, diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index 18b71afc364..0b648a81553 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -19,7 +19,6 @@ slot_clock = { workspace = true } types = { workspace = true } safe_arith = { workspace = true } serde = { workspace = true } -serde_derive = "1.0.116" bincode = { workspace = true } serde_json = { workspace = true } slog = { workspace = true } diff --git a/validator_client/slashing_protection/Cargo.toml b/validator_client/slashing_protection/Cargo.toml index cc90c979b9a..3224e61fff6 100644 --- a/validator_client/slashing_protection/Cargo.toml +++ b/validator_client/slashing_protection/Cargo.toml @@ -16,7 +16,6 @@ rusqlite = { workspace = true } r2d2 = { workspace = true } r2d2_sqlite = "0.21.0" serde = { workspace = true } -serde_derive = "1.0.116" serde_json = { workspace = true } ethereum_serde_utils = { workspace = true } filesystem = { workspace = true } diff --git a/validator_client/slashing_protection/src/interchange.rs b/validator_client/slashing_protection/src/interchange.rs index 99d37c38b9b..ad5f21e5110 100644 --- a/validator_client/slashing_protection/src/interchange.rs +++ b/validator_client/slashing_protection/src/interchange.rs @@ -1,5 +1,5 @@ use crate::InterchangeError; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::cmp::max; use std::collections::{HashMap, HashSet}; use std::io; diff --git a/validator_client/slashing_protection/src/interchange_test.rs b/validator_client/slashing_protection/src/interchange_test.rs index dc828773b9c..1bb1fc550bf 100644 --- a/validator_client/slashing_protection/src/interchange_test.rs +++ b/validator_client/slashing_protection/src/interchange_test.rs @@ -3,7 +3,7 @@ use crate::{ test_utils::{pubkey, DEFAULT_GENESIS_VALIDATORS_ROOT}, SigningRoot, SlashingDatabase, }; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::collections::HashSet; use tempfile::tempdir; use types::{Epoch, Hash256, PublicKeyBytes, Slot}; diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 7c662db9371..aa5ce6c983c 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -8,7 +8,7 @@ use directory::{ }; use eth2::types::Graffiti; use sensitive_url::SensitiveUrl; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use slog::{info, warn, Logger}; use std::fs; use std::net::IpAddr; diff --git a/validator_client/src/graffiti_file.rs b/validator_client/src/graffiti_file.rs index 5c1f84e10b3..29da3dca5a7 100644 --- a/validator_client/src/graffiti_file.rs +++ b/validator_client/src/graffiti_file.rs @@ -1,4 +1,4 @@ -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs::File; use std::io::{prelude::*, BufReader}; From 38e7172508c1c66c058b685d661b12155fde1dd3 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Fri, 13 Oct 2023 01:13:08 +1100 Subject: [PATCH 07/21] Add `blob_sidecar` event to SSE (#4790) * Add `blob_sidecar` event to SSE. * Return 202 if a block is published but failed blob validation when validation level is `Gossip`. * Move `BlobSidecar` event to `process_gossip_blob` and add test. * Emit `BlobSidecar` event when blobs are received over rpc. * Improve test assertions on `SseBlobSidecar`s. * Add quotes to blob index serialization in `SseBlobSidecar` Co-authored-by: realbigsean --------- Co-authored-by: realbigsean --- beacon_node/beacon_chain/src/beacon_chain.rs | 20 +++- .../beacon_chain/src/blob_verification.rs | 8 +- beacon_node/beacon_chain/src/events.rs | 15 +++ beacon_node/beacon_chain/tests/events.rs | 99 +++++++++++++++++++ beacon_node/beacon_chain/tests/main.rs | 1 + beacon_node/http_api/src/lib.rs | 3 + beacon_node/http_api/src/publish_blocks.rs | 14 ++- .../network_beacon_processor/sync_methods.rs | 4 +- common/eth2/src/types.rs | 30 ++++++ 9 files changed, 187 insertions(+), 7 deletions(-) create mode 100644 beacon_node/beacon_chain/tests/events.rs diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 14733a89ab4..918d0bd29b7 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -70,7 +70,7 @@ use crate::{ kzg_utils, metrics, AvailabilityPendingExecutedBlock, BeaconChainError, BeaconForkChoiceStore, BeaconSnapshot, CachedHead, }; -use eth2::types::{EventKind, SseBlock, SseExtendedPayloadAttributes, SyncDuty}; +use eth2::types::{EventKind, SseBlobSidecar, SseBlock, SseExtendedPayloadAttributes, SyncDuty}; use execution_layer::{ BlockProposalContents, BuilderParams, ChainHealth, ExecutionLayer, FailedCondition, PayloadAttributes, PayloadStatus, @@ -2809,6 +2809,14 @@ impl BeaconChain { return Err(BlockError::BlockIsAlreadyKnown); } + if let Some(event_handler) = self.event_handler.as_ref() { + if event_handler.has_blob_sidecar_subscribers() { + event_handler.register(EventKind::BlobSidecar(SseBlobSidecar::from_blob_sidecar( + blob.as_blob(), + ))); + } + } + self.data_availability_checker .notify_gossip_blob(blob.as_blob().slot, block_root, &blob); let r = self.check_gossip_blob_availability_and_import(blob).await; @@ -2833,6 +2841,16 @@ impl BeaconChain { return Err(BlockError::BlockIsAlreadyKnown); } + if let Some(event_handler) = self.event_handler.as_ref() { + if event_handler.has_blob_sidecar_subscribers() { + for blob in blobs.iter().filter_map(|maybe_blob| maybe_blob.as_ref()) { + event_handler.register(EventKind::BlobSidecar( + SseBlobSidecar::from_blob_sidecar(blob), + )); + } + } + } + self.data_availability_checker .notify_rpc_blobs(slot, block_root, &blobs); let r = self diff --git a/beacon_node/beacon_chain/src/blob_verification.rs b/beacon_node/beacon_chain/src/blob_verification.rs index b78859e10fe..1c8bc9be85b 100644 --- a/beacon_node/beacon_chain/src/blob_verification.rs +++ b/beacon_node/beacon_chain/src/blob_verification.rs @@ -10,7 +10,7 @@ use crate::block_verification::cheap_state_advance_to_obtain_committees; use crate::data_availability_checker::AvailabilityCheckError; use crate::kzg_utils::{validate_blob, validate_blobs}; use crate::{metrics, BeaconChainError}; -use kzg::Kzg; +use kzg::{Kzg, KzgCommitment}; use slog::{debug, warn}; use ssz_derive::{Decode, Encode}; use ssz_types::VariableList; @@ -182,6 +182,12 @@ impl GossipVerifiedBlob { pub fn slot(&self) -> Slot { self.blob.message.slot } + pub fn index(&self) -> u64 { + self.blob.message.index + } + pub fn kzg_commitment(&self) -> KzgCommitment { + self.blob.message.kzg_commitment + } pub fn proposer_index(&self) -> u64 { self.blob.message.proposer_index } diff --git a/beacon_node/beacon_chain/src/events.rs b/beacon_node/beacon_chain/src/events.rs index b267cc853f8..8d3a6827946 100644 --- a/beacon_node/beacon_chain/src/events.rs +++ b/beacon_node/beacon_chain/src/events.rs @@ -9,6 +9,7 @@ const DEFAULT_CHANNEL_CAPACITY: usize = 16; pub struct ServerSentEventHandler { attestation_tx: Sender>, block_tx: Sender>, + blob_sidecar_tx: Sender>, finalized_tx: Sender>, head_tx: Sender>, exit_tx: Sender>, @@ -31,6 +32,7 @@ impl ServerSentEventHandler { pub fn new_with_capacity(log: Logger, capacity: usize) -> Self { let (attestation_tx, _) = broadcast::channel(capacity); let (block_tx, _) = broadcast::channel(capacity); + let (blob_sidecar_tx, _) = broadcast::channel(capacity); let (finalized_tx, _) = broadcast::channel(capacity); let (head_tx, _) = broadcast::channel(capacity); let (exit_tx, _) = broadcast::channel(capacity); @@ -43,6 +45,7 @@ impl ServerSentEventHandler { Self { attestation_tx, block_tx, + blob_sidecar_tx, finalized_tx, head_tx, exit_tx, @@ -73,6 +76,10 @@ impl ServerSentEventHandler { .block_tx .send(kind) .map(|count| log_count("block", count)), + EventKind::BlobSidecar(_) => self + .blob_sidecar_tx + .send(kind) + .map(|count| log_count("blob sidecar", count)), EventKind::FinalizedCheckpoint(_) => self .finalized_tx .send(kind) @@ -119,6 +126,10 @@ impl ServerSentEventHandler { self.block_tx.subscribe() } + pub fn subscribe_blob_sidecar(&self) -> Receiver> { + self.blob_sidecar_tx.subscribe() + } + pub fn subscribe_finalized(&self) -> Receiver> { self.finalized_tx.subscribe() } @@ -159,6 +170,10 @@ impl ServerSentEventHandler { self.block_tx.receiver_count() > 0 } + pub fn has_blob_sidecar_subscribers(&self) -> bool { + self.blob_sidecar_tx.receiver_count() > 0 + } + pub fn has_finalized_subscribers(&self) -> bool { self.finalized_tx.receiver_count() > 0 } diff --git a/beacon_node/beacon_chain/tests/events.rs b/beacon_node/beacon_chain/tests/events.rs new file mode 100644 index 00000000000..c48cf310a2a --- /dev/null +++ b/beacon_node/beacon_chain/tests/events.rs @@ -0,0 +1,99 @@ +use beacon_chain::blob_verification::GossipVerifiedBlob; +use beacon_chain::test_utils::BeaconChainHarness; +use bls::Signature; +use eth2::types::{EventKind, SseBlobSidecar}; +use rand::rngs::StdRng; +use rand::SeedableRng; +use std::marker::PhantomData; +use std::sync::Arc; +use types::blob_sidecar::FixedBlobSidecarList; +use types::{BlobSidecar, EthSpec, ForkName, MinimalEthSpec, SignedBlobSidecar}; + +type E = MinimalEthSpec; + +/// Verifies that a blob event is emitted when a gossip verified blob is received via gossip or the publish block API. +#[tokio::test] +async fn blob_sidecar_event_on_process_gossip_blob() { + let spec = ForkName::Deneb.make_genesis_spec(E::default_spec()); + let harness = BeaconChainHarness::builder(E::default()) + .spec(spec) + .deterministic_keypairs(8) + .fresh_ephemeral_store() + .mock_execution_layer() + .build(); + + // subscribe to blob sidecar events + let event_handler = harness.chain.event_handler.as_ref().unwrap(); + let mut blob_event_receiver = event_handler.subscribe_blob_sidecar(); + + // build and process a gossip verified blob + let kzg = harness.chain.kzg.as_ref().unwrap(); + let mut rng = StdRng::seed_from_u64(0xDEADBEEF0BAD5EEDu64); + let signed_sidecar = SignedBlobSidecar { + message: BlobSidecar::random_valid(&mut rng, kzg) + .map(Arc::new) + .unwrap(), + signature: Signature::empty(), + _phantom: PhantomData, + }; + let gossip_verified_blob = GossipVerifiedBlob::__assumed_valid(signed_sidecar); + let expected_sse_blobs = SseBlobSidecar::from_blob_sidecar(gossip_verified_blob.as_blob()); + + let _ = harness + .chain + .process_gossip_blob(gossip_verified_blob) + .await + .unwrap(); + + let sidecar_event = blob_event_receiver.try_recv().unwrap(); + assert_eq!(sidecar_event, EventKind::BlobSidecar(expected_sse_blobs)); +} + +/// Verifies that a blob event is emitted when blobs are received via RPC. +#[tokio::test] +async fn blob_sidecar_event_on_process_rpc_blobs() { + let spec = ForkName::Deneb.make_genesis_spec(E::default_spec()); + let harness = BeaconChainHarness::builder(E::default()) + .spec(spec) + .deterministic_keypairs(8) + .fresh_ephemeral_store() + .mock_execution_layer() + .build(); + + // subscribe to blob sidecar events + let event_handler = harness.chain.event_handler.as_ref().unwrap(); + let mut blob_event_receiver = event_handler.subscribe_blob_sidecar(); + + // build and process multiple rpc blobs + let kzg = harness.chain.kzg.as_ref().unwrap(); + let mut rng = StdRng::seed_from_u64(0xDEADBEEF0BAD5EEDu64); + + let blob_1 = BlobSidecar::random_valid(&mut rng, kzg) + .map(Arc::new) + .unwrap(); + let blob_2 = Arc::new(BlobSidecar { + index: 1, + ..BlobSidecar::random_valid(&mut rng, kzg).unwrap() + }); + let blobs = FixedBlobSidecarList::from(vec![Some(blob_1.clone()), Some(blob_2.clone())]); + let expected_sse_blobs = vec![ + SseBlobSidecar::from_blob_sidecar(blob_1.as_ref()), + SseBlobSidecar::from_blob_sidecar(blob_2.as_ref()), + ]; + + let _ = harness + .chain + .process_rpc_blobs(blob_1.slot, blob_1.block_root, blobs) + .await + .unwrap(); + + let mut sse_blobs: Vec = vec![]; + while let Ok(sidecar_event) = blob_event_receiver.try_recv() { + if let EventKind::BlobSidecar(sse_blob_sidecar) = sidecar_event { + sse_blobs.push(sse_blob_sidecar); + } else { + panic!("`BlobSidecar` event kind expected."); + } + } + assert_eq!(sse_blobs, expected_sse_blobs); +} diff --git a/beacon_node/beacon_chain/tests/main.rs b/beacon_node/beacon_chain/tests/main.rs index c81a547406a..332f6a48298 100644 --- a/beacon_node/beacon_chain/tests/main.rs +++ b/beacon_node/beacon_chain/tests/main.rs @@ -2,6 +2,7 @@ mod attestation_production; mod attestation_verification; mod block_verification; mod capella; +mod events; mod merge; mod op_verification; mod payload_invalidation; diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index ca81ac5082b..1b83b8531e7 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -4425,6 +4425,9 @@ pub fn serve( let receiver = match topic { api_types::EventTopic::Head => event_handler.subscribe_head(), api_types::EventTopic::Block => event_handler.subscribe_block(), + api_types::EventTopic::BlobSidecar => { + event_handler.subscribe_blob_sidecar() + } api_types::EventTopic::Attestation => { event_handler.subscribe_attestation() } diff --git a/beacon_node/http_api/src/publish_blocks.rs b/beacon_node/http_api/src/publish_blocks.rs index a0e63ec9e9e..cd9ccb6c5c6 100644 --- a/beacon_node/http_api/src/publish_blocks.rs +++ b/beacon_node/http_api/src/publish_blocks.rs @@ -199,9 +199,17 @@ pub async fn publish_block &msg + ); + Err(warp_utils::reject::custom_bad_request(msg)) + }; } } } diff --git a/beacon_node/network/src/network_beacon_processor/sync_methods.rs b/beacon_node/network/src/network_beacon_processor/sync_methods.rs index d2d589b35ad..d6bb7421e87 100644 --- a/beacon_node/network/src/network_beacon_processor/sync_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/sync_methods.rs @@ -260,13 +260,13 @@ impl NetworkBeaconProcessor { pub fn generate_rpc_blobs_process_fn( self: Arc, block_root: Hash256, - block: FixedBlobSidecarList, + blobs: FixedBlobSidecarList, seen_timestamp: Duration, process_type: BlockProcessType, ) -> AsyncFn { let process_fn = async move { self.clone() - .process_rpc_blobs(block_root, block, seen_timestamp, process_type) + .process_rpc_blobs(block_root, blobs, seen_timestamp, process_type) .await; }; Box::pin(process_fn) diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index cc790f467ff..7f3040a9b0b 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -888,6 +888,28 @@ pub struct SseBlock { pub execution_optimistic: bool, } +#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)] +pub struct SseBlobSidecar { + pub block_root: Hash256, + #[serde(with = "serde_utils::quoted_u64")] + pub index: u64, + pub slot: Slot, + pub kzg_commitment: KzgCommitment, + pub versioned_hash: VersionedHash, +} + +impl SseBlobSidecar { + pub fn from_blob_sidecar(blob_sidecar: &BlobSidecar) -> SseBlobSidecar { + SseBlobSidecar { + block_root: blob_sidecar.block_root, + index: blob_sidecar.index, + slot: blob_sidecar.slot, + kzg_commitment: blob_sidecar.kzg_commitment, + versioned_hash: blob_sidecar.kzg_commitment.calculate_versioned_hash(), + } + } +} + #[derive(PartialEq, Debug, Serialize, Deserialize, Clone)] pub struct SseFinalizedCheckpoint { pub block: Hash256, @@ -1018,6 +1040,7 @@ impl ForkVersionDeserialize for SseExtendedPayloadAttributes { pub enum EventKind { Attestation(Box>), Block(SseBlock), + BlobSidecar(SseBlobSidecar), FinalizedCheckpoint(SseFinalizedCheckpoint), Head(SseHead), VoluntaryExit(SignedVoluntaryExit), @@ -1034,6 +1057,7 @@ impl EventKind { match self { EventKind::Head(_) => "head", EventKind::Block(_) => "block", + EventKind::BlobSidecar(_) => "blob_sidecar", EventKind::Attestation(_) => "attestation", EventKind::VoluntaryExit(_) => "voluntary_exit", EventKind::FinalizedCheckpoint(_) => "finalized_checkpoint", @@ -1071,6 +1095,9 @@ impl EventKind { "block" => Ok(EventKind::Block(serde_json::from_str(data).map_err( |e| ServerError::InvalidServerSentEvent(format!("Block: {:?}", e)), )?)), + "blob_sidecar" => Ok(EventKind::BlobSidecar(serde_json::from_str(data).map_err( + |e| ServerError::InvalidServerSentEvent(format!("Blob Sidecar: {:?}", e)), + )?)), "chain_reorg" => Ok(EventKind::ChainReorg(serde_json::from_str(data).map_err( |e| ServerError::InvalidServerSentEvent(format!("Chain Reorg: {:?}", e)), )?)), @@ -1123,6 +1150,7 @@ pub struct EventQuery { pub enum EventTopic { Head, Block, + BlobSidecar, Attestation, VoluntaryExit, FinalizedCheckpoint, @@ -1141,6 +1169,7 @@ impl FromStr for EventTopic { match s { "head" => Ok(EventTopic::Head), "block" => Ok(EventTopic::Block), + "blob_sidecar" => Ok(EventTopic::BlobSidecar), "attestation" => Ok(EventTopic::Attestation), "voluntary_exit" => Ok(EventTopic::VoluntaryExit), "finalized_checkpoint" => Ok(EventTopic::FinalizedCheckpoint), @@ -1160,6 +1189,7 @@ impl fmt::Display for EventTopic { match self { EventTopic::Head => write!(f, "head"), EventTopic::Block => write!(f, "block"), + EventTopic::BlobSidecar => write!(f, "blob_sidecar"), EventTopic::Attestation => write!(f, "attestation"), EventTopic::VoluntaryExit => write!(f, "voluntary_exit"), EventTopic::FinalizedCheckpoint => write!(f, "finalized_checkpoint"), From 2d662f78aefe6406d3c9f26846e6635a3f78295c Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 16 Oct 2023 16:24:27 +1100 Subject: [PATCH 08/21] Use Deneb fork in generate_genesis_header Co-authored-by: Jimmy Chen --- .../execution_layer/src/test_utils/execution_block_generator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index c191fb2a00e..ebfcd08750c 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -711,7 +711,7 @@ pub fn generate_genesis_header( Some(header) } ForkName::Deneb => { - let mut header = ExecutionPayloadHeader::Capella(<_>::default()); + let mut header = ExecutionPayloadHeader::Deneb(<_>::default()); *header.block_hash_mut() = genesis_block_hash.unwrap_or_default(); Some(header) } From cf544b3996444883eac3201ed06451aed22eed23 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 16 Oct 2023 16:30:14 +1100 Subject: [PATCH 09/21] Very minor own nitpicks (#4845) --- beacon_node/lighthouse_network/src/config.rs | 2 -- beacon_node/network/src/router.rs | 2 +- beacon_node/network/src/sync/block_lookups/mod.rs | 4 ++-- book/src/developers.md | 3 ++- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/beacon_node/lighthouse_network/src/config.rs b/beacon_node/lighthouse_network/src/config.rs index fb005e08943..5d84753781f 100644 --- a/beacon_node/lighthouse_network/src/config.rs +++ b/beacon_node/lighthouse_network/src/config.rs @@ -468,8 +468,6 @@ pub fn gossipsub_config( ) -> Vec { let topic_bytes = message.topic.as_str().as_bytes(); match fork_context.current_fork() { - // according to: https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/p2p-interface.md#the-gossip-domain-gossipsub - // the derivation of the message-id remains the same in the merge and for eip 4844. ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => { let topic_len_bytes = topic_bytes.len().to_le_bytes(); let mut vec = Vec::with_capacity( diff --git a/beacon_node/network/src/router.rs b/beacon_node/network/src/router.rs index 7248147178e..4df940a3b79 100644 --- a/beacon_node/network/src/router.rs +++ b/beacon_node/network/src/router.rs @@ -540,7 +540,7 @@ impl Router { seen_timestamp: timestamp_now(), }); } else { - debug!( + crit!( self.log, "All blobs by range responses should belong to sync" ); diff --git a/beacon_node/network/src/sync/block_lookups/mod.rs b/beacon_node/network/src/sync/block_lookups/mod.rs index fd9d0d6e0e4..3f1ecd129b6 100644 --- a/beacon_node/network/src/sync/block_lookups/mod.rs +++ b/beacon_node/network/src/sync/block_lookups/mod.rs @@ -49,7 +49,7 @@ pub type DownloadedBlock = (Hash256, RpcBlock); const FAILED_CHAINS_CACHE_EXPIRY_SECONDS: u64 = 60; pub const SINGLE_BLOCK_LOOKUP_MAX_ATTEMPTS: u8 = 3; -/// This enum is used to track what a peer *should* be able to respond with respond based on +/// This enum is used to track what a peer *should* be able to respond with based on /// other messages we've seen from this peer on the network. This is useful for peer scoring. /// We expect a peer tracked by the `BlockAndBlobs` variant to be able to respond to all /// components of a block. This peer has either sent an attestation for the requested block @@ -449,7 +449,7 @@ impl BlockLookups { } } CachedChild::DownloadIncomplete => { - // If this was the result of a block request, we can't determined if the block peer + // If this was the result of a block request, we can't determine if the block peer // did anything wrong. If we already had both a block and blobs response processed, // we should penalize the blobs peer because they did not provide all blobs on the // initial request. diff --git a/book/src/developers.md b/book/src/developers.md index 2ba09bd3412..ab12bed5b94 100644 --- a/book/src/developers.md +++ b/book/src/developers.md @@ -48,4 +48,5 @@ custom RPC error messages. | Code | Message | Description | | ---- | ---- | ---- | -| 139 | Rate Limited | The peer has been rate limited so we return this error as a response | \ No newline at end of file +| 139 | Rate Limited | The peer has been rate limited so we return this error as a response | +| 140 | Blobs Not Found For Block | We do not possess the blobs for the requested block | From 283ec8cf2465d547c8b1b06e327f2f915500c927 Mon Sep 17 00:00:00 2001 From: realbigsean Date: Mon, 16 Oct 2023 18:53:46 -0400 Subject: [PATCH 10/21] Deneb pr updates 2 (#4851) * use workspace deps in kzg crate * delete unused blobs dp path field * full match on fork name in engine api get payload v3 * only accept v3 payloads on get payload v3 endpoint in mock el * remove FIXMEs related to merge transition tests * move static tx to test utils * default max_per_epoch_activation_churn_limit to mainnet value * remove unnecessary async * remove comment * use task executor in `blob_sidecars` endpoint --- Cargo.lock | 1 - beacon_node/client/src/builder.rs | 3 -- .../execution_layer/src/engine_api/http.rs | 7 ++--- beacon_node/execution_layer/src/lib.rs | 27 +--------------- .../test_utils/execution_block_generator.rs | 31 +++++++++++++++++-- .../src/test_utils/handle_rpc.rs | 15 +-------- .../src/test_utils/mock_execution_layer.rs | 12 ++----- beacon_node/http_api/src/block_id.rs | 6 ++-- beacon_node/http_api/src/lib.rs | 10 +++--- beacon_node/http_api/src/publish_blocks.rs | 3 +- consensus/types/src/chain_spec.rs | 6 +++- crypto/kzg/Cargo.toml | 19 ++++++------ 12 files changed, 60 insertions(+), 80 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 979921faedd..70b77406432 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3829,7 +3829,6 @@ dependencies = [ "ethereum_ssz_derive", "hex", "serde", - "serde_derive", "tree_hash", ] diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index d0cfb3825b5..d1184cf75de 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -71,7 +71,6 @@ pub struct ClientBuilder { gossipsub_registry: Option, db_path: Option, freezer_db_path: Option, - blobs_db_path: Option, http_api_config: http_api::Config, http_metrics_config: http_metrics::Config, slasher: Option>>, @@ -106,7 +105,6 @@ where gossipsub_registry: None, db_path: None, freezer_db_path: None, - blobs_db_path: None, http_api_config: <_>::default(), http_metrics_config: <_>::default(), slasher: None, @@ -927,7 +925,6 @@ where self.db_path = Some(hot_path.into()); self.freezer_db_path = Some(cold_path.into()); - self.blobs_db_path = blobs_path.clone(); let inner_spec = spec.clone(); let deposit_contract_deploy_block = context diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index c6806171084..ac7dfa57e92 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -897,10 +897,9 @@ impl HttpJsonRpc { .await?; Ok(JsonGetPayloadResponse::V3(response).into()) } - _ => Err(Error::UnsupportedForkVariant(format!( - "called get_payload_v3 with {}", - fork_name - ))), + ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => Err( + Error::UnsupportedForkVariant(format!("called get_payload_v3 with {}", fork_name)), + ), } } diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 9eb19bb2b73..034400487c1 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -48,7 +48,7 @@ use types::{ AbstractExecPayload, BeaconStateError, BlindedPayload, BlockType, ChainSpec, Epoch, ExecPayload, ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadMerge, }; -use types::{ProposerPreparationData, PublicKeyBytes, Signature, Slot, Transaction}; +use types::{ProposerPreparationData, PublicKeyBytes, Signature, Slot}; mod block_hash; mod engine_api; @@ -2163,31 +2163,6 @@ fn timestamp_now() -> u64 { .as_secs() } -fn static_valid_tx() -> Result, String> { - // This is a real transaction hex encoded, but we don't care about the contents of the transaction. - let transaction: EthersTransaction = serde_json::from_str( - r#"{ - "blockHash":"0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2", - "blockNumber":"0x5daf3b", - "from":"0xa7d9ddbe1f17865597fbd27ec712455208b6b76d", - "gas":"0xc350", - "gasPrice":"0x4a817c800", - "hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b", - "input":"0x68656c6c6f21", - "nonce":"0x15", - "to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb", - "transactionIndex":"0x41", - "value":"0xf3dbb76162000", - "v":"0x25", - "r":"0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea", - "s":"0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c" - }"#, - ) - .unwrap(); - VariableList::new(transaction.rlp().to_vec()) - .map_err(|e| format!("Failed to convert transaction to SSZ: {:?}", e)) -} - fn noop( _: &ExecutionLayer, _: PayloadContentsRefTuple, diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index ebfcd08750c..6b638bb7f27 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -1,4 +1,5 @@ use crate::engines::ForkchoiceState; +use crate::EthersTransaction; use crate::{ engine_api::{ json_structures::{ @@ -6,13 +7,14 @@ use crate::{ }, ExecutionBlock, PayloadAttributes, PayloadId, PayloadStatusV1, PayloadStatusV1Status, }, - static_valid_tx, ExecutionBlockWithTransactions, + ExecutionBlockWithTransactions, }; use eth2::types::BlobsBundle; use kzg::Kzg; use parking_lot::Mutex; use rand::{rngs::StdRng, Rng, SeedableRng}; use serde::{Deserialize, Serialize}; +use ssz_types::VariableList; use std::collections::HashMap; use std::sync::Arc; use tree_hash::TreeHash; @@ -20,7 +22,7 @@ use tree_hash_derive::TreeHash; use types::{ BlobSidecar, ChainSpec, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadHeader, ExecutionPayloadMerge, ForkName, Hash256, - Transactions, Uint256, + Transaction, Transactions, Uint256, }; use super::DEFAULT_TERMINAL_BLOCK; @@ -681,6 +683,31 @@ pub fn generate_random_blobs( Ok((bundle, transactions.into())) } +fn static_valid_tx() -> Result, String> { + // This is a real transaction hex encoded, but we don't care about the contents of the transaction. + let transaction: EthersTransaction = serde_json::from_str( + r#"{ + "blockHash":"0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2", + "blockNumber":"0x5daf3b", + "from":"0xa7d9ddbe1f17865597fbd27ec712455208b6b76d", + "gas":"0xc350", + "gasPrice":"0x4a817c800", + "hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b", + "input":"0x68656c6c6f21", + "nonce":"0x15", + "to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb", + "transactionIndex":"0x41", + "value":"0xf3dbb76162000", + "v":"0x25", + "r":"0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea", + "s":"0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c" + }"#, + ) + .unwrap(); + VariableList::new(transaction.rlp().to_vec()) + .map_err(|e| format!("Failed to convert transaction to SSZ: {:?}", e)) +} + fn payload_id_from_u64(n: u64) -> PayloadId { n.to_le_bytes() } diff --git a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs index e50e6f8d379..9dff1ac0089 100644 --- a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs +++ b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs @@ -282,20 +282,6 @@ pub async fn handle_rpc( _ => unreachable!(), }), ENGINE_GET_PAYLOAD_V3 => Ok(match JsonExecutionPayload::from(response) { - JsonExecutionPayload::V1(execution_payload) => { - serde_json::to_value(JsonGetPayloadResponseV1 { - execution_payload, - block_value: DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI.into(), - }) - .unwrap() - } - JsonExecutionPayload::V2(execution_payload) => { - serde_json::to_value(JsonGetPayloadResponseV2 { - execution_payload, - block_value: DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI.into(), - }) - .unwrap() - } JsonExecutionPayload::V3(execution_payload) => { serde_json::to_value(JsonGetPayloadResponseV3 { execution_payload, @@ -310,6 +296,7 @@ pub async fn handle_rpc( }) .unwrap() } + _ => unreachable!(), }), _ => unreachable!(), } diff --git a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs index f1bd89868c8..2ba51bd67de 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs @@ -103,14 +103,8 @@ impl MockExecutionLayer { justified_hash: None, finalized_hash: None, }; - let payload_attributes = PayloadAttributes::new( - timestamp, - prev_randao, - Address::repeat_byte(42), - // FIXME: think about how to handle different forks here.. - None, - None, - ); + let payload_attributes = + PayloadAttributes::new(timestamp, prev_randao, Address::repeat_byte(42), None, None); // Insert a proposer to ensure the fork choice updated command works. let slot = Slot::new(0); @@ -146,7 +140,6 @@ impl MockExecutionLayer { &payload_attributes, forkchoice_update_params, builder_params, - // FIXME: do we need to consider other forks somehow? ForkName::Merge, &self.spec, ) @@ -181,7 +174,6 @@ impl MockExecutionLayer { &payload_attributes, forkchoice_update_params, builder_params, - // FIXME: do we need to consider other forks somehow? What about withdrawals? ForkName::Merge, &self.spec, ) diff --git a/beacon_node/http_api/src/block_id.rs b/beacon_node/http_api/src/block_id.rs index 545213ca81c..45fc651f05c 100644 --- a/beacon_node/http_api/src/block_id.rs +++ b/beacon_node/http_api/src/block_id.rs @@ -253,7 +253,7 @@ impl BlockId { } /// Return the `BlobSidecarList` identified by `self`. - pub async fn blob_sidecar_list( + pub fn blob_sidecar_list( &self, chain: &BeaconChain, ) -> Result, warp::Rejection> { @@ -263,12 +263,12 @@ impl BlockId { .map_err(warp_utils::reject::beacon_chain_error) } - pub async fn blob_sidecar_list_filtered( + pub fn blob_sidecar_list_filtered( &self, indices: BlobIndicesQuery, chain: &BeaconChain, ) -> Result, warp::Rejection> { - let blob_sidecar_list = self.blob_sidecar_list(chain).await?; + let blob_sidecar_list = self.blob_sidecar_list(chain)?; let blob_sidecar_list_filtered = match indices.indices { Some(vec) => { let list = blob_sidecar_list diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 1b83b8531e7..05c678b250f 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -1729,16 +1729,18 @@ pub fn serve( .and(block_id_or_err) .and(warp::query::()) .and(warp::path::end()) + .and(task_spawner_filter.clone()) .and(chain_filter.clone()) .and(warp::header::optional::("accept")) - .and_then( + .then( |block_id: BlockId, indices: api_types::BlobIndicesQuery, + task_spawner: TaskSpawner, chain: Arc>, accept_header: Option| { - async move { + task_spawner.blocking_response_task(Priority::P1, move || { let blob_sidecar_list_filtered = - block_id.blob_sidecar_list_filtered(indices, &chain).await?; + block_id.blob_sidecar_list_filtered(indices, &chain)?; match accept_header { Some(api_types::Accept::Ssz) => Response::builder() .status(200) @@ -1755,7 +1757,7 @@ pub fn serve( )) .into_response()), } - } + }) }, ); diff --git a/beacon_node/http_api/src/publish_blocks.rs b/beacon_node/http_api/src/publish_blocks.rs index cd9ccb6c5c6..e68691ce8b9 100644 --- a/beacon_node/http_api/src/publish_blocks.rs +++ b/beacon_node/http_api/src/publish_blocks.rs @@ -75,8 +75,7 @@ pub async fn publish_block block.slot(), "publish_delay" => ?publish_delay); - // Send the block, regardless of whether or not it is valid. The API - // specification is very clear that this is the desired behaviour. + match block.as_ref() { SignedBeaconBlock::Base(_) | SignedBeaconBlock::Altair(_) diff --git a/consensus/types/src/chain_spec.rs b/consensus/types/src/chain_spec.rs index 680db57c227..ed002978580 100644 --- a/consensus/types/src/chain_spec.rs +++ b/consensus/types/src/chain_spec.rs @@ -1019,7 +1019,7 @@ pub struct Config { ejection_balance: u64, #[serde(with = "serde_utils::quoted_u64")] min_per_epoch_churn_limit: u64, - #[serde(default)] + #[serde(default = "default_max_per_epoch_activation_churn_limit")] #[serde(with = "serde_utils::quoted_u64")] max_per_epoch_activation_churn_limit: u64, #[serde(with = "serde_utils::quoted_u64")] @@ -1106,6 +1106,10 @@ fn default_subnets_per_node() -> u8 { 2u8 } +const fn default_max_per_epoch_activation_churn_limit() -> u64 { + 8 +} + const fn default_gossip_max_size() -> u64 { 10485760 } diff --git a/crypto/kzg/Cargo.toml b/crypto/kzg/Cargo.toml index b1e93379544..d652ecb4c1d 100644 --- a/crypto/kzg/Cargo.toml +++ b/crypto/kzg/Cargo.toml @@ -7,18 +7,17 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ethereum_ssz = "0.5.0" -ethereum_ssz_derive = "0.5.3" -tree_hash = "0.5.2" -derivative = "2.1.1" -serde = "1.0.116" -serde_derive = "1.0.116" -ethereum_serde_utils = "0.5.0" -hex = "0.4.2" -ethereum_hashing = "1.0.0-beta.2" +arbitrary = { workspace = true } +ethereum_ssz = { workspace = true } +ethereum_ssz_derive = { workspace = true } +tree_hash = { workspace = true } +derivative = { workspace = true } +serde = { workspace = true } +ethereum_serde_utils = { workspace = true } +hex = { workspace = true } +ethereum_hashing = { workspace = true } c-kzg = { git = "https://github.com/ethereum/c-kzg-4844", rev = "f5f6f863d475847876a2bd5ee252058d37c3a15d" , features = ["mainnet-spec", "serde"]} c_kzg_min = { package = "c-kzg", git = "https://github.com/ethereum//c-kzg-4844", rev = "f5f6f863d475847876a2bd5ee252058d37c3a15d", features = ["minimal-spec", "serde"], optional = true } -arbitrary = { version = "1.0", features = ["derive"] } [features] # TODO(deneb): enabled by default for convenience, would need more cfg magic to disable From 369b624b19da6617c133966a42d96b5abbac1569 Mon Sep 17 00:00:00 2001 From: Mac L Date: Wed, 18 Oct 2023 04:08:55 +0000 Subject: [PATCH 11/21] Fix broken Nethermind integration tests (#4836) ## Issue Addressed CI is currently blocked by persistently failing integration tests. ## Proposed Changes Use latest Nethermind release and apply the appropriate fixes as there have been breaking changes. Also increase the timeout since I had some local timeouts. Co-authored-by: Michael Sproul Co-authored-by: antondlr Co-authored-by: Jimmy Chen --- .github/workflows/test-suite.yml | 6 ++- lighthouse/tests/beacon_node.rs | 2 +- .../src/build_utils.rs | 1 + .../src/genesis_json.rs | 33 +++++++----- .../execution_engine_integration/src/geth.rs | 11 +++- .../execution_engine_integration/src/main.rs | 3 ++ .../src/nethermind.rs | 15 ++++-- .../src/test_rig.rs | 51 +++++++++++-------- 8 files changed, 82 insertions(+), 40 deletions(-) diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 1d80feaddf4..f0811257ae0 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -317,10 +317,11 @@ jobs: ./doppelganger_protection.sh success genesis.json execution-engine-integration-ubuntu: name: execution-engine-integration-ubuntu - runs-on: ubuntu-latest + runs-on: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "linux", "CI", "small"]') || 'ubuntu-latest' }} steps: - uses: actions/checkout@v3 - name: Get latest version of stable Rust + if: env.SELF_HOSTED_RUNNERS == 'false' uses: moonrepo/setup-rust@v1 with: channel: stable @@ -328,6 +329,9 @@ jobs: cache: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Add go compiler to $PATH + if: env.SELF_HOSTED_RUNNERS == 'true' + run: echo "/usr/local/go/bin" >> $GITHUB_PATH - name: Run exec engine integration tests in release run: make test-exec-engine check-code: diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index 95172980f65..7937b1d496e 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -835,7 +835,7 @@ fn run_jwt_optional_flags_test(jwt_flag: &str, jwt_id_flag: &str, jwt_version_fl let id = "bn-1"; let version = "Lighthouse-v2.1.3"; CommandLineTest::new() - .flag("execution-endpoint", Some(execution_endpoint.clone())) + .flag("execution-endpoint", Some(execution_endpoint)) .flag(jwt_flag, dir.path().join(jwt_file).as_os_str().to_str()) .flag(jwt_id_flag, Some(id)) .flag(jwt_version_flag, Some(version)) diff --git a/testing/execution_engine_integration/src/build_utils.rs b/testing/execution_engine_integration/src/build_utils.rs index 15e7fdc0f1e..5d965206604 100644 --- a/testing/execution_engine_integration/src/build_utils.rs +++ b/testing/execution_engine_integration/src/build_utils.rs @@ -66,6 +66,7 @@ pub fn get_latest_release(repo_dir: &Path, branch_name: &str) -> Result Value { "muirGlacierBlock":0, "berlinBlock":0, "londonBlock":0, - "clique": { - "period": 5, - "epoch": 30000 - }, - "terminalTotalDifficulty":0 + "mergeNetsplitBlock": 0, + "shanghaiTime": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true }, "nonce":"0x42", "timestamp":"0x0", @@ -72,8 +71,10 @@ pub fn nethermind_genesis_json() -> Value { "accountStartNonce": "0x0", "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", - "networkID": "0x1469ca", - "MergeForkIdTransition": "0x3e8", + "networkID": "0x00146A2E", + "MergeForkIdTransition": "0x0", + "maxCodeSize": "0x6000", + "maxCodeSizeTransition": "0x0", "eip150Transition": "0x0", "eip158Transition": "0x0", "eip160Transition": "0x0", @@ -101,7 +102,15 @@ pub fn nethermind_genesis_json() -> Value { "eip1559Transition": "0x0", "eip3198Transition": "0x0", "eip3529Transition": "0x0", - "eip3541Transition": "0x0" + "eip3541Transition": "0x0", + "eip3540TransitionTimestamp": "0x0", + "eip3651TransitionTimestamp": "0x0", + "eip3670TransitionTimestamp": "0x0", + "eip3675TransitionTimestamp": "0x0", + "eip3855TransitionTimestamp": "0x0", + "eip3860TransitionTimestamp": "0x0", + "eip4895TransitionTimestamp": "0x0", + "terminalTotalDifficulty": "0x0" }, "genesis": { "seal": { @@ -112,10 +121,10 @@ pub fn nethermind_genesis_json() -> Value { }, "difficulty": "0x01", "author": "0x0000000000000000000000000000000000000000", - "timestamp": "0x0", + "timestamp": "0x63585F88", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "", - "gasLimit": "0x1C9C380" + "gasLimit": "0x400000" }, "accounts": { "0x7b8C3a386C0eea54693fFB0DA17373ffC9228139": { @@ -123,9 +132,9 @@ pub fn nethermind_genesis_json() -> Value { }, "0xdA2DD7560DB7e212B945fC72cEB54B7D8C886D77": { "balance": "10000000000000000000000000" - }, + } }, "nodes": [] - } + } ) } diff --git a/testing/execution_engine_integration/src/geth.rs b/testing/execution_engine_integration/src/geth.rs index 5c83a97e21f..0bd96a5c933 100644 --- a/testing/execution_engine_integration/src/geth.rs +++ b/testing/execution_engine_integration/src/geth.rs @@ -3,7 +3,7 @@ use crate::execution_engine::GenericExecutionEngine; use crate::genesis_json::geth_genesis_json; use std::path::{Path, PathBuf}; use std::process::{Child, Command, Output}; -use std::{env, fs::File}; +use std::{env, fs}; use tempfile::TempDir; use unused_port::unused_tcp4_port; @@ -36,6 +36,13 @@ pub fn build(execution_clients_dir: &Path) { }); } +pub fn clean(execution_clients_dir: &Path) { + let repo_dir = execution_clients_dir.join("go-ethereum"); + if let Err(e) = fs::remove_dir_all(repo_dir) { + eprintln!("Error while deleting folder: {}", e); + } +} + /* * Geth-specific Implementation for GenericExecutionEngine */ @@ -60,7 +67,7 @@ impl GenericExecutionEngine for GethEngine { let datadir = TempDir::new().unwrap(); let genesis_json_path = datadir.path().join("genesis.json"); - let mut file = File::create(&genesis_json_path).unwrap(); + let mut file = fs::File::create(&genesis_json_path).unwrap(); let json = geth_genesis_json(); serde_json::to_writer(&mut file, &json).unwrap(); diff --git a/testing/execution_engine_integration/src/main.rs b/testing/execution_engine_integration/src/main.rs index e46bc13c8d3..efb06833f63 100644 --- a/testing/execution_engine_integration/src/main.rs +++ b/testing/execution_engine_integration/src/main.rs @@ -1,3 +1,5 @@ +#![recursion_limit = "256"] // for inline json + /// This binary runs integration tests between Lighthouse and execution engines. /// /// It will first attempt to build any supported integration clients, then it will run tests. @@ -31,6 +33,7 @@ fn test_geth() { let test_dir = build_utils::prepare_dir(); geth::build(&test_dir); TestRig::new(GethEngine).perform_tests_blocking(); + geth::clean(&test_dir); } fn test_nethermind() { diff --git a/testing/execution_engine_integration/src/nethermind.rs b/testing/execution_engine_integration/src/nethermind.rs index 8925f1cc84b..aad37c32bd1 100644 --- a/testing/execution_engine_integration/src/nethermind.rs +++ b/testing/execution_engine_integration/src/nethermind.rs @@ -2,7 +2,7 @@ use crate::build_utils; use crate::execution_engine::GenericExecutionEngine; use crate::genesis_json::nethermind_genesis_json; use std::env; -use std::fs::File; +use std::fs; use std::path::{Path, PathBuf}; use std::process::{Child, Command, Output}; use tempfile::TempDir; @@ -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.18.2"; +const NETHERMIND_BRANCH: &str = "release/1.21.0"; const NETHERMIND_REPO_URL: &str = "https://github.com/NethermindEth/nethermind"; fn build_result(repo_dir: &Path) -> Output { @@ -47,6 +47,12 @@ pub fn build(execution_clients_dir: &Path) { build_utils::check_command_output(build_result(&repo_dir), || { format!("nethermind build failed using release {last_release}") }); + + // Cleanup some disk space by removing nethermind's tests + let tests_dir = execution_clients_dir.join("nethermind/src/tests"); + if let Err(e) = fs::remove_dir_all(tests_dir) { + eprintln!("Error while deleting folder: {}", e); + } } /* @@ -68,7 +74,8 @@ impl NethermindEngine { .join("bin") .join("Release") .join("net7.0") - .join("Nethermind.Runner") + .join("linux-x64") + .join("nethermind") } } @@ -76,7 +83,7 @@ impl GenericExecutionEngine for NethermindEngine { fn init_datadir() -> TempDir { let datadir = TempDir::new().unwrap(); let genesis_json_path = datadir.path().join("genesis.json"); - let mut file = File::create(genesis_json_path).unwrap(); + let mut file = fs::File::create(genesis_json_path).unwrap(); let json = nethermind_genesis_json(); serde_json::to_writer(&mut file, &json).unwrap(); datadir diff --git a/testing/execution_engine_integration/src/test_rig.rs b/testing/execution_engine_integration/src/test_rig.rs index 2aaff30f577..48195f871dc 100644 --- a/testing/execution_engine_integration/src/test_rig.rs +++ b/testing/execution_engine_integration/src/test_rig.rs @@ -18,7 +18,9 @@ use types::{ Address, ChainSpec, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadHeader, ForkName, FullPayload, Hash256, MainnetEthSpec, PublicKeyBytes, Slot, Uint256, }; -const EXECUTION_ENGINE_START_TIMEOUT: Duration = Duration::from_secs(30); +const EXECUTION_ENGINE_START_TIMEOUT: Duration = Duration::from_secs(60); + +const TEST_FORK: ForkName = ForkName::Capella; struct ExecutionPair { /// The Lighthouse `ExecutionLayer` struct, connected to the `execution_engine` via HTTP. @@ -110,7 +112,7 @@ impl TestRig { let (runtime_shutdown, exit) = exit_future::signal(); let (shutdown_tx, _) = futures::channel::mpsc::channel(1); let executor = TaskExecutor::new(Arc::downgrade(&runtime), exit, log.clone(), shutdown_tx); - let mut spec = MainnetEthSpec::default_spec(); + let mut spec = TEST_FORK.make_genesis_spec(MainnetEthSpec::default_spec()); spec.terminal_total_difficulty = Uint256::zero(); let fee_recipient = None; @@ -269,12 +271,11 @@ impl TestRig { Slot::new(1), // Insert proposer for the next slot head_root, proposer_index, - // TODO: think about how to test different forks PayloadAttributes::new( timestamp, prev_randao, Address::repeat_byte(42), - None, + Some(vec![]), None, ), ) @@ -314,8 +315,13 @@ impl TestRig { .execution_layer .get_suggested_fee_recipient(proposer_index) .await; - let payload_attributes = - PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None, None); + let payload_attributes = PayloadAttributes::new( + timestamp, + prev_randao, + suggested_fee_recipient, + Some(vec![]), + None, + ); let valid_payload = self .ee_a .execution_layer @@ -324,8 +330,7 @@ impl TestRig { &payload_attributes, forkchoice_update_params, builder_params, - // FIXME: think about how to test other forks - ForkName::Merge, + TEST_FORK, &self.spec, ) .await @@ -456,8 +461,13 @@ impl TestRig { .execution_layer .get_suggested_fee_recipient(proposer_index) .await; - let payload_attributes = - PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None, None); + let payload_attributes = PayloadAttributes::new( + timestamp, + prev_randao, + suggested_fee_recipient, + Some(vec![]), + None, + ); let second_payload = self .ee_a .execution_layer @@ -466,8 +476,7 @@ impl TestRig { &payload_attributes, forkchoice_update_params, builder_params, - // FIXME: think about how to test other forks - ForkName::Merge, + TEST_FORK, &self.spec, ) .await @@ -498,11 +507,15 @@ impl TestRig { */ let head_block_hash = valid_payload.block_hash(); let finalized_block_hash = ExecutionBlockHash::zero(); - // TODO: think about how to handle different forks // To save sending proposer preparation data, just set the fee recipient // to the fee recipient configured for EE A. - let payload_attributes = - PayloadAttributes::new(timestamp, prev_randao, Address::repeat_byte(42), None, None); + let payload_attributes = PayloadAttributes::new( + timestamp, + prev_randao, + Address::repeat_byte(42), + Some(vec![]), + None, + ); let slot = Slot::new(42); let head_block_root = Hash256::repeat_byte(100); let validator_index = 0; @@ -536,11 +549,7 @@ impl TestRig { .notify_new_payload(second_payload.clone().try_into().unwrap()) .await .unwrap(); - // TODO: we should remove the `Accepted` status here once Geth fixes it - assert!(matches!( - status, - PayloadStatus::Syncing | PayloadStatus::Accepted - )); + assert!(matches!(status, PayloadStatus::Syncing)); /* * Execution Engine B: @@ -641,11 +650,13 @@ async fn check_payload_reconstruction( .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()]) From 64c156c0c1b102208b4c6c254e4ddebfc8402f6b Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Wed, 18 Oct 2023 04:40:29 +0000 Subject: [PATCH 12/21] Pre-generate test blobs bundle to improve test time. (#4829) ## Issue Addressed Addresses #4778, and potentially fixes the flaky deneb builder test `builder_works_post_deneb`. The [deneb builder test](https://github.com/sigp/lighthouse/blob/c5c84f12138c6fb6b2d0317c467f66cc0f4f8111/beacon_node/http_api/tests/tests.rs#L5371) has been quite flaky on our CI (`release-tests`) since it was introduced. I'm guessing that it might be timing out on the builder `get_header` call (1 second), and therefore the local payload is used, while the test expects builder payload to be used. On my machine the [`get_header` ](https://github.com/sigp/lighthouse/blob/c5c84f12138c6fb6b2d0317c467f66cc0f4f8111/beacon_node/execution_layer/src/test_utils/mock_builder.rs#L367) call takes about 550ms, which could easily go over 1s on slower environments (our windows CI runner is much slower than the ubuntu one). I did a profile on the test and it showed that `blob_to_kzg_commiment` and `compute_kzg_proof` was taking a large chunk of time, so perhaps pre-generating the blobs could help stablise this test. ## Proposed Changes Pre-generate blobs bundle for Mainnet and Minimal presets. Before the change `get_header` took about **550ms**, and it's now reduced to **50-55ms** after the change. If timeout was indeed the cause of the flaky test, this fix should stablise it. This also brings the flaky `builder_works_post_deneb` test time from 50s to 10s. (8s if we only use a single blob) --- Cargo.lock | 1 + .../availability_view.rs | 8 +- beacon_node/beacon_chain/src/test_utils.rs | 4 +- beacon_node/execution_layer/Cargo.toml | 1 + .../test_utils/execution_block_generator.rs | 100 ++++++++++++++---- .../fixtures/mainnet/test_blobs_bundle.ssz | Bin 0 -> 131180 bytes .../fixtures/minimal/test_blobs_bundle.ssz | Bin 0 -> 236 bytes .../execution_layer/src/test_utils/mod.rs | 4 +- .../network/src/sync/block_lookups/tests.rs | 4 +- common/eth2/src/types.rs | 4 +- 10 files changed, 86 insertions(+), 40 deletions(-) create mode 100644 beacon_node/execution_layer/src/test_utils/fixtures/mainnet/test_blobs_bundle.ssz create mode 100644 beacon_node/execution_layer/src/test_utils/fixtures/minimal/test_blobs_bundle.ssz diff --git a/Cargo.lock b/Cargo.lock index 70b77406432..b2ab1e28e0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2531,6 +2531,7 @@ dependencies = [ "bytes", "environment", "eth2", + "eth2_network_config", "ethereum_serde_utils", "ethereum_ssz", "ethers-core", diff --git a/beacon_node/beacon_chain/src/data_availability_checker/availability_view.rs b/beacon_node/beacon_chain/src/data_availability_checker/availability_view.rs index fea2ee101ee..f013cf649a8 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker/availability_view.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker/availability_view.rs @@ -267,9 +267,7 @@ pub mod tests { use crate::test_utils::{generate_rand_block_and_blobs, NumBlobs}; use crate::AvailabilityPendingExecutedBlock; use crate::PayloadVerificationOutcome; - use eth2_network_config::get_trusted_setup; use fork_choice::PayloadVerificationStatus; - use kzg::{Kzg, TrustedSetup}; use rand::rngs::StdRng; use rand::SeedableRng; use state_processing::ConsensusContext; @@ -285,13 +283,9 @@ pub mod tests { ); pub fn pre_setup() -> Setup { - let trusted_setup: TrustedSetup = - serde_json::from_reader(get_trusted_setup::<::Kzg>()).unwrap(); - let kzg = Kzg::new_from_trusted_setup(trusted_setup).unwrap(); - let mut rng = StdRng::seed_from_u64(0xDEADBEEF0BAD5EEDu64); let (block, blobs_vec) = - generate_rand_block_and_blobs::(ForkName::Deneb, NumBlobs::Random, &kzg, &mut rng); + generate_rand_block_and_blobs::(ForkName::Deneb, NumBlobs::Random, &mut rng); let mut blobs: FixedVector<_, ::MaxBlobsPerBlock> = FixedVector::default(); for blob in blobs_vec { diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index a4abb90104e..a77da4c8e53 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -2526,7 +2526,6 @@ pub enum NumBlobs { pub fn generate_rand_block_and_blobs( fork_name: ForkName, num_blobs: NumBlobs, - kzg: &Kzg, rng: &mut impl Rng, ) -> (SignedBeaconBlock>, Vec>) { let inner = map_fork_name!(fork_name, BeaconBlock, <_>::random_for_test(rng)); @@ -2540,8 +2539,7 @@ pub fn generate_rand_block_and_blobs( NumBlobs::None => 0, }; let (bundle, transactions) = - execution_layer::test_utils::generate_random_blobs::(num_blobs, kzg, rng) - .unwrap(); + execution_layer::test_utils::generate_blobs::(num_blobs).unwrap(); payload.execution_payload.transactions = <_>::default(); for tx in Vec::from(transactions) { diff --git a/beacon_node/execution_layer/Cargo.toml b/beacon_node/execution_layer/Cargo.toml index 46c585bb05a..7f652689806 100644 --- a/beacon_node/execution_layer/Cargo.toml +++ b/beacon_node/execution_layer/Cargo.toml @@ -50,3 +50,4 @@ triehash = "0.8.4" hash-db = "0.15.2" pretty_reqwest_error = { workspace = true } arc-swap = "1.6.0" +eth2_network_config = { workspace = true } diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index 6b638bb7f27..b91c9b51a0e 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -10,7 +10,7 @@ use crate::{ ExecutionBlockWithTransactions, }; use eth2::types::BlobsBundle; -use kzg::Kzg; +use kzg::{Kzg, KzgCommitment, KzgProof}; use parking_lot::Mutex; use rand::{rngs::StdRng, Rng, SeedableRng}; use serde::{Deserialize, Serialize}; @@ -20,12 +20,16 @@ use std::sync::Arc; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; use types::{ - BlobSidecar, ChainSpec, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadCapella, - ExecutionPayloadDeneb, ExecutionPayloadHeader, ExecutionPayloadMerge, ForkName, Hash256, - Transaction, Transactions, Uint256, + Blob, ChainSpec, EthSpec, EthSpecId, ExecutionBlockHash, ExecutionPayload, + ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadHeader, ExecutionPayloadMerge, + ForkName, Hash256, Transaction, Transactions, Uint256, }; use super::DEFAULT_TERMINAL_BLOCK; +use ssz::Decode; + +const TEST_BLOB_BUNDLE_MAINNET: &[u8] = include_bytes!("fixtures/mainnet/test_blobs_bundle.ssz"); +const TEST_BLOB_BUNDLE_MINIMAL: &[u8] = include_bytes!("fixtures/minimal/test_blobs_bundle.ssz"); const GAS_LIMIT: u64 = 16384; const GAS_USED: u64 = GAS_LIMIT - 1; @@ -627,8 +631,7 @@ impl ExecutionBlockGenerator { // get random number between 0 and Max Blobs let mut rng = self.rng.lock(); let num_blobs = rng.gen::() % (T::max_blobs_per_block() + 1); - let kzg = self.kzg.as_ref().ok_or("kzg not initialized")?; - let (bundle, transactions) = generate_random_blobs(num_blobs, kzg, &mut *rng)?; + let (bundle, transactions) = generate_blobs(num_blobs)?; for tx in Vec::from(transactions) { execution_payload .transactions_mut() @@ -645,30 +648,51 @@ impl ExecutionBlockGenerator { } } -pub fn generate_random_blobs( +pub fn load_test_blobs_bundle() -> Result<(KzgCommitment, KzgProof, Blob), String> { + let blob_bundle_bytes = match E::spec_name() { + EthSpecId::Mainnet => TEST_BLOB_BUNDLE_MAINNET, + EthSpecId::Minimal => TEST_BLOB_BUNDLE_MINIMAL, + EthSpecId::Gnosis => { + return Err("Test blobs bundle not available for Gnosis preset".to_string()) + } + }; + + let BlobsBundle { + commitments, + proofs, + blobs, + } = BlobsBundle::::from_ssz_bytes(blob_bundle_bytes) + .map_err(|e| format!("Unable to decode SSZ: {:?}", e))?; + + Ok(( + commitments + .get(0) + .cloned() + .ok_or("commitment missing in test bundle")?, + proofs + .get(0) + .cloned() + .ok_or("proof missing in test bundle")?, + blobs.get(0).cloned().ok_or("blob missing in test bundle")?, + )) +} + +pub fn generate_blobs( n_blobs: usize, - kzg: &Kzg, - rng: &mut R, -) -> Result<(BlobsBundle, Transactions), String> { - let mut bundle = BlobsBundle::::default(); - let mut transactions = vec![]; - for blob_index in 0..n_blobs { - let random_valid_sidecar = BlobSidecar::::random_valid(rng, kzg)?; +) -> Result<(BlobsBundle, Transactions), String> { + let (kzg_commitment, kzg_proof, blob) = load_test_blobs_bundle::()?; - let BlobSidecar { - blob, - kzg_commitment, - kzg_proof, - .. - } = random_valid_sidecar; + let mut bundle = BlobsBundle::::default(); + let mut transactions = vec![]; - let tx = static_valid_tx::() + for blob_index in 0..n_blobs { + let tx = static_valid_tx::() .map_err(|e| format!("error creating valid tx SSZ bytes: {:?}", e))?; transactions.push(tx); bundle .blobs - .push(blob) + .push(blob.clone()) .map_err(|_| format!("blobs are full, blob index: {:?}", blob_index))?; bundle .commitments @@ -797,7 +821,8 @@ pub fn generate_pow_block( #[cfg(test)] mod test { use super::*; - use types::MainnetEthSpec; + use kzg::TrustedSetup; + use types::{MainnetEthSpec, MinimalEthSpec}; #[test] fn pow_chain_only() { @@ -859,4 +884,33 @@ mod test { assert!(generator.block_by_number(next_i).is_none()); } } + + #[test] + fn valid_test_blobs() { + assert!( + validate_blob::().unwrap(), + "Mainnet preset test blobs bundle should contain valid proofs" + ); + assert!( + validate_blob::().unwrap(), + "Minimal preset test blobs bundle should contain valid proofs" + ); + } + + fn validate_blob() -> Result { + let kzg = load_kzg::()?; + let (kzg_commitment, kzg_proof, blob) = load_test_blobs_bundle::()?; + let kzg_blob = E::blob_from_bytes(blob.as_ref()) + .map_err(|e| format!("Error converting blob to kzg blob: {e:?}"))?; + kzg.verify_blob_kzg_proof(&kzg_blob, kzg_commitment, kzg_proof) + .map_err(|e| format!("Invalid blobs bundle: {e:?}")) + } + + fn load_kzg() -> Result, String> { + let trusted_setup: TrustedSetup = + serde_json::from_reader(eth2_network_config::get_trusted_setup::()) + .map_err(|e| format!("Unable to read trusted setup file: {e:?}"))?; + Kzg::new_from_trusted_setup(trusted_setup) + .map_err(|e| format!("Failed to load trusted setup: {e:?}")) + } } diff --git a/beacon_node/execution_layer/src/test_utils/fixtures/mainnet/test_blobs_bundle.ssz b/beacon_node/execution_layer/src/test_utils/fixtures/mainnet/test_blobs_bundle.ssz new file mode 100644 index 0000000000000000000000000000000000000000..8ef325a00cf97476a55a1dc1975f53f6ac285149 GIT binary patch literal 131180 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

#_Sdtw{9cz7pYf;T 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�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ϋ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 J)?+OE^*=h1ZKMDI literal 0 HcmV?d00001 diff --git a/beacon_node/execution_layer/src/test_utils/fixtures/minimal/test_blobs_bundle.ssz b/beacon_node/execution_layer/src/test_utils/fixtures/minimal/test_blobs_bundle.ssz new file mode 100644 index 0000000000000000000000000000000000000000..366a467b2c1eb60d9cba69d66e8471eab4571da0 GIT binary patch literal 236 zcmV*tlEO5SHNa6a(B!9Pzbl=>o1=!Z^$je=*4buP?IY2US7j zS3q1|0*~8+pJ{Qj8#I-2H)~FyPm7oUOV1g#n?q`+)rbWZ$72?bfr^>2=zl2K^TbQ0 mcID&%Y>&=y&81 (SignedBeaconBlock, Vec>) { - let kzg = self.harness.chain.kzg.as_ref().unwrap(); let rng = &mut self.rng; - - generate_rand_block_and_blobs::(fork_name, num_blobs, kzg.as_ref(), rng) + generate_rand_block_and_blobs::(fork_name, num_blobs, rng) } #[track_caller] diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index 7f3040a9b0b..e87f254f10c 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -7,7 +7,7 @@ use mediatype::{names, MediaType, MediaTypeList}; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use ssz::{Decode, DecodeError}; -use ssz_derive::Encode; +use ssz_derive::{Decode, Encode}; use std::convert::TryFrom; use std::fmt::{self, Display}; use std::str::{from_utf8, FromStr}; @@ -2012,7 +2012,7 @@ pub struct ExecutionPayloadAndBlobs { pub blobs_bundle: BlobsBundle, } -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, Encode)] +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, Encode, Decode)] #[serde(bound = "E: EthSpec")] pub struct BlobsBundle { pub commitments: KzgCommitments, From 5cc0f1097b9a3b32967e5cb15b497cdc74f72ed2 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Wed, 18 Oct 2023 06:52:50 +0000 Subject: [PATCH 13/21] Fix metric for total block production time (#4794) ## Proposed Changes Fix the misplacement of the total block production time metric, which occurred during a previous refactor. Total block production times are no longer skewed low (data from Holesky + blockdreamer): ``` # HELP beacon_block_production_seconds Full runtime of block production # TYPE beacon_block_production_seconds histogram beacon_block_production_seconds_bucket{le="0.005"} 0 beacon_block_production_seconds_bucket{le="0.01"} 0 beacon_block_production_seconds_bucket{le="0.025"} 0 beacon_block_production_seconds_bucket{le="0.05"} 0 beacon_block_production_seconds_bucket{le="0.1"} 0 beacon_block_production_seconds_bucket{le="0.25"} 0 beacon_block_production_seconds_bucket{le="0.5"} 37 beacon_block_production_seconds_bucket{le="1"} 65 beacon_block_production_seconds_bucket{le="2.5"} 66 beacon_block_production_seconds_bucket{le="5"} 66 beacon_block_production_seconds_bucket{le="10"} 66 beacon_block_production_seconds_bucket{le="+Inf"} 66 beacon_block_production_seconds_sum 34.225780452 beacon_block_production_seconds_count 66 ``` ## Additional Info Cheers to @jimmygchen for helping spot this. --- beacon_node/beacon_chain/src/beacon_chain.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 918d0bd29b7..a45caba79c4 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -3978,6 +3978,9 @@ impl BeaconChain { validator_graffiti: Option, verification: ProduceBlockVerification, ) -> Result, BlockProductionError> { + metrics::inc_counter(&metrics::BLOCK_PRODUCTION_REQUESTS); + let _complete_timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_TIMES); + // Part 1/2 (blocking) // // Load the parent state from disk. @@ -4012,9 +4015,6 @@ impl BeaconChain { self: &Arc, slot: Slot, ) -> Result<(BeaconState, Option), BlockProductionError> { - metrics::inc_counter(&metrics::BLOCK_PRODUCTION_REQUESTS); - let _complete_timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_TIMES); - let fork_choice_timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_FORK_CHOICE_TIMES); self.wait_for_fork_choice_before_block_production(slot)?; drop(fork_choice_timer); From 18f3edff0a9de499eac2cee21623edf120bcff8e Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Wed, 18 Oct 2023 06:52:51 +0000 Subject: [PATCH 14/21] Add `vendor` directory to `.gitignore` (#4819) ## Issue Addressed The vendor directory gets populated after running `cargo vendor`. This directory should be ignored by VCS. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bbae3145414..e63e218a3bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ target/ +vendor/ **/*.rs.bk *.pk *.sk From f10d3d07c3e8536dba4aaa2cd239645597c9573b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Oliveira?= Date: Wed, 18 Oct 2023 06:52:52 +0000 Subject: [PATCH 15/21] remove crit! logging from ListenerClosed event on Ok() (#4821) ## Issue Addressed Since adding Quic support on https://github.com/sigp/lighthouse/pull/4577, and due to `quinn`s api nature LH now triggers the [`ListenerClosed`](https://docs.rs/libp2p/0.52.3/libp2p/swarm/struct.ListenerClosed.html) event.. @michaelsproul noticed we are logging this event as `crit!` independently of the reason. This PR matches the reason, logging with `debug!` and `error!` (instead of `crit!`) according to its `Result` ## Additional Info LH will still log `crit!` until https://github.com/libp2p/rust-libp2p/pull/4621 has been merged --- beacon_node/lighthouse_network/src/service/mod.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/beacon_node/lighthouse_network/src/service/mod.rs b/beacon_node/lighthouse_network/src/service/mod.rs index 53b6bcab37d..5daa6557eaa 100644 --- a/beacon_node/lighthouse_network/src/service/mod.rs +++ b/beacon_node/lighthouse_network/src/service/mod.rs @@ -1602,7 +1602,14 @@ impl Network { SwarmEvent::ListenerClosed { addresses, reason, .. } => { - crit!(self.log, "Listener closed"; "addresses" => ?addresses, "reason" => ?reason); + match reason { + Ok(_) => { + debug!(self.log, "Listener gracefuly closed"; "addresses" => ?addresses) + } + Err(reason) => { + crit!(self.log, "Listener abruptly closed"; "addresses" => ?addresses, "reason" => ?reason) + } + }; if Swarm::listeners(&self.swarm).count() == 0 { Some(NetworkEvent::ZeroListeners) } else { From a7c46bf7eda913e9f38d6a48b0b1b34aa72bd177 Mon Sep 17 00:00:00 2001 From: Mac L Date: Wed, 18 Oct 2023 06:52:53 +0000 Subject: [PATCH 16/21] Fix Homebrew link (#4822) ## Issue Addressed N/A ## Proposed Changes I saw a false positive on the link-check CI run and while investigating I noticed that this link technically 404's but is not "dead" in the strict sense. I have updated it to the correct path. --- book/src/homebrew.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/homebrew.md b/book/src/homebrew.md index 317dc0e0fa6..486de371f86 100644 --- a/book/src/homebrew.md +++ b/book/src/homebrew.md @@ -31,6 +31,6 @@ Alternatively, you can find the `lighthouse` binary at: The [formula][] is kept up-to-date by the Homebrew community and a bot that lists for new releases. -The package source can be found in the [homebrew-core](https://github.com/Homebrew/homebrew-core/blob/master/Formula/lighthouse.rb) repo. +The package source can be found in the [homebrew-core](https://github.com/Homebrew/homebrew-core/blob/master/Formula/l/lighthouse.rb) repo. [formula]: https://formulae.brew.sh/formula/lighthouse From 1b4545cd9d05a001a8295c526a1e83d1e4f83fd3 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Wed, 18 Oct 2023 06:52:54 +0000 Subject: [PATCH 17/21] Remove blob clones in KZG verification (#4852) ## Issue Addressed This PR removes two instances of blob clones during blob verification that may not be necessary. --- beacon_node/beacon_chain/src/beacon_chain.rs | 2 +- beacon_node/beacon_chain/src/blob_verification.rs | 14 ++++---------- beacon_node/beacon_chain/src/kzg_utils.rs | 10 +++++----- .../src/cases/kzg_verify_blob_kzg_proof.rs | 2 +- .../src/cases/kzg_verify_blob_kzg_proof_batch.rs | 2 +- 5 files changed, 12 insertions(+), 18 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index a45caba79c4..78e1cc46fdf 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -5123,7 +5123,7 @@ impl BeaconChain { kzg_utils::validate_blobs::( kzg, expected_kzg_commitments, - blobs, + blobs.iter().collect(), &kzg_proofs, ) .map_err(BlockProductionError::KzgError)?; diff --git a/beacon_node/beacon_chain/src/blob_verification.rs b/beacon_node/beacon_chain/src/blob_verification.rs index 1c8bc9be85b..b535f3104c6 100644 --- a/beacon_node/beacon_chain/src/blob_verification.rs +++ b/beacon_node/beacon_chain/src/blob_verification.rs @@ -502,8 +502,7 @@ pub fn verify_kzg_for_blob( kzg: &Kzg, ) -> Result, AvailabilityCheckError> { let _timer = crate::metrics::start_timer(&crate::metrics::KZG_VERIFICATION_SINGLE_TIMES); - //TODO(sean) remove clone - if validate_blob::(kzg, blob.blob.clone(), blob.kzg_commitment, blob.kzg_proof) + if validate_blob::(kzg, &blob.blob, blob.kzg_commitment, blob.kzg_proof) .map_err(AvailabilityCheckError::Kzg)? { Ok(KzgVerifiedBlob { blob }) @@ -524,15 +523,10 @@ pub fn verify_kzg_for_blob_list( let _timer = crate::metrics::start_timer(&crate::metrics::KZG_VERIFICATION_BATCH_TIMES); let (blobs, (commitments, proofs)): (Vec<_>, (Vec<_>, Vec<_>)) = blob_list .iter() - .map(|blob| (blob.blob.clone(), (blob.kzg_commitment, blob.kzg_proof))) + .map(|blob| (&blob.blob, (blob.kzg_commitment, blob.kzg_proof))) .unzip(); - if validate_blobs::( - kzg, - commitments.as_slice(), - blobs.as_slice(), - proofs.as_slice(), - ) - .map_err(AvailabilityCheckError::Kzg)? + if validate_blobs::(kzg, commitments.as_slice(), blobs, proofs.as_slice()) + .map_err(AvailabilityCheckError::Kzg)? { Ok(()) } else { diff --git a/beacon_node/beacon_chain/src/kzg_utils.rs b/beacon_node/beacon_chain/src/kzg_utils.rs index 144e2136758..b4c70befa01 100644 --- a/beacon_node/beacon_chain/src/kzg_utils.rs +++ b/beacon_node/beacon_chain/src/kzg_utils.rs @@ -12,12 +12,12 @@ fn ssz_blob_to_crypto_blob( /// Validate a single blob-commitment-proof triplet from a `BlobSidecar`. pub fn validate_blob( kzg: &Kzg, - blob: Blob, + blob: &Blob, kzg_commitment: KzgCommitment, kzg_proof: KzgProof, ) -> Result { kzg.verify_blob_kzg_proof( - &ssz_blob_to_crypto_blob::(&blob)?, + &ssz_blob_to_crypto_blob::(blob)?, kzg_commitment, kzg_proof, ) @@ -27,12 +27,12 @@ pub fn validate_blob( pub fn validate_blobs( kzg: &Kzg, expected_kzg_commitments: &[KzgCommitment], - blobs: &[Blob], + blobs: Vec<&Blob>, kzg_proofs: &[KzgProof], ) -> Result { let blobs = blobs - .iter() - .map(|blob| ssz_blob_to_crypto_blob::(blob)) // Avoid this clone + .into_iter() + .map(|blob| ssz_blob_to_crypto_blob::(blob)) .collect::, KzgError>>()?; kzg.verify_blob_kzg_proof_batch(&blobs, expected_kzg_commitments, kzg_proofs) diff --git a/testing/ef_tests/src/cases/kzg_verify_blob_kzg_proof.rs b/testing/ef_tests/src/cases/kzg_verify_blob_kzg_proof.rs index 2d18c9bdc0b..ff918cac134 100644 --- a/testing/ef_tests/src/cases/kzg_verify_blob_kzg_proof.rs +++ b/testing/ef_tests/src/cases/kzg_verify_blob_kzg_proof.rs @@ -91,7 +91,7 @@ impl Case for KZGVerifyBlobKZGProof { let kzg = get_kzg::()?; let result = parse_input(&self.input).and_then(|(blob, commitment, proof)| { - validate_blob::(&kzg, blob, commitment, proof) + validate_blob::(&kzg, &blob, commitment, proof) .map_err(|e| Error::InternalError(format!("Failed to validate blob: {:?}", e))) }); diff --git a/testing/ef_tests/src/cases/kzg_verify_blob_kzg_proof_batch.rs b/testing/ef_tests/src/cases/kzg_verify_blob_kzg_proof_batch.rs index cc941618a75..43c2c8cf6c3 100644 --- a/testing/ef_tests/src/cases/kzg_verify_blob_kzg_proof_batch.rs +++ b/testing/ef_tests/src/cases/kzg_verify_blob_kzg_proof_batch.rs @@ -54,7 +54,7 @@ impl Case for KZGVerifyBlobKZGProofBatch { let kzg = get_kzg::()?; let result = parse_input(&self.input).and_then(|(commitments, blobs, proofs)| { - validate_blobs::(&kzg, &commitments, &blobs, &proofs) + validate_blobs::(&kzg, &commitments, blobs.iter().collect(), &proofs) .map_err(|e| Error::InternalError(format!("Failed to validate blobs: {:?}", e))) }); From 463e62e83327e885ac887f76b8766e9cde79bc6a Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Wed, 18 Oct 2023 12:59:53 +0000 Subject: [PATCH 18/21] Generalise compare_fields to work with iterators (#4823) ## Proposed Changes Add `compare_fields(as_iter)` as a field attribute to `compare_fields_derive`. This allows any iterable type to be compared in the same as a slice (by index). This is forwards-compatible with tree-states types like `List` and `Vector` which can not be cast to slices. --- Cargo.lock | 1 + common/compare_fields/Cargo.toml | 3 +++ common/compare_fields/src/lib.rs | 36 ++++++++++++++++++++----- common/compare_fields_derive/src/lib.rs | 13 ++++----- 4 files changed, 40 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b2ab1e28e0b..083be58c2cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1120,6 +1120,7 @@ name = "compare_fields" version = "0.2.0" dependencies = [ "compare_fields_derive", + "itertools", ] [[package]] diff --git a/common/compare_fields/Cargo.toml b/common/compare_fields/Cargo.toml index 8df989e7225..9972ca75ca6 100644 --- a/common/compare_fields/Cargo.toml +++ b/common/compare_fields/Cargo.toml @@ -4,6 +4,9 @@ version = "0.2.0" authors = ["Paul Hauner "] edition = { workspace = true } +[dependencies] +itertools = { workspace = true } + [dev-dependencies] compare_fields_derive = { workspace = true } diff --git a/common/compare_fields/src/lib.rs b/common/compare_fields/src/lib.rs index bc2f5446ad2..27baf148067 100644 --- a/common/compare_fields/src/lib.rs +++ b/common/compare_fields/src/lib.rs @@ -81,11 +81,8 @@ //! } //! ]; //! assert_eq!(bar_a.compare_fields(&bar_b), bar_a_b); -//! -//! -//! -//! // TODO: //! ``` +use itertools::{EitherOrBoth, Itertools}; use std::fmt::Debug; #[derive(Debug, PartialEq, Clone)] @@ -112,13 +109,38 @@ impl Comparison { } pub fn from_slice>(field_name: String, a: &[T], b: &[T]) -> Self { + Self::from_iter(field_name, a.iter(), b.iter()) + } + + pub fn from_into_iter<'a, T: Debug + PartialEq + 'a>( + field_name: String, + a: impl IntoIterator, + b: impl IntoIterator, + ) -> Self { + Self::from_iter(field_name, a.into_iter(), b.into_iter()) + } + + pub fn from_iter<'a, T: Debug + PartialEq + 'a>( + field_name: String, + a: impl Iterator, + b: impl Iterator, + ) -> Self { let mut children = vec![]; + let mut all_equal = true; - for i in 0..std::cmp::max(a.len(), b.len()) { - children.push(FieldComparison::new(format!("{i}"), &a.get(i), &b.get(i))); + for (i, entry) in a.zip_longest(b).enumerate() { + let comparison = match entry { + EitherOrBoth::Both(x, y) => { + FieldComparison::new(format!("{i}"), &Some(x), &Some(y)) + } + EitherOrBoth::Left(x) => FieldComparison::new(format!("{i}"), &Some(x), &None), + EitherOrBoth::Right(y) => FieldComparison::new(format!("{i}"), &None, &Some(y)), + }; + all_equal = all_equal && comparison.equal(); + children.push(comparison); } - Self::parent(field_name, a == b, children) + Self::parent(field_name, all_equal, children) } pub fn retain_children(&mut self, f: F) diff --git a/common/compare_fields_derive/src/lib.rs b/common/compare_fields_derive/src/lib.rs index a8b92b3d548..099db8e791e 100644 --- a/common/compare_fields_derive/src/lib.rs +++ b/common/compare_fields_derive/src/lib.rs @@ -4,10 +4,11 @@ use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, DeriveInput}; -fn is_slice(field: &syn::Field) -> bool { +fn is_iter(field: &syn::Field) -> bool { field.attrs.iter().any(|attr| { attr.path.is_ident("compare_fields") - && attr.tokens.to_string().replace(' ', "") == "(as_slice)" + && (attr.tokens.to_string().replace(' ', "") == "(as_slice)" + || attr.tokens.to_string().replace(' ', "") == "(as_iter)") }) } @@ -34,13 +35,13 @@ pub fn compare_fields_derive(input: TokenStream) -> TokenStream { let field_name = ident_a.to_string(); let ident_b = ident_a.clone(); - let quote = if is_slice(field) { + let quote = if is_iter(field) { quote! { - comparisons.push(compare_fields::Comparison::from_slice( + comparisons.push(compare_fields::Comparison::from_into_iter( #field_name.to_string(), &self.#ident_a, - &b.#ident_b) - ); + &b.#ident_b + )); } } else { quote! { From 192d44271849e744fc7ec0c574c6564eea49f015 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Wed, 18 Oct 2023 13:36:42 +0000 Subject: [PATCH 19/21] Fix Rayon deadlock in test utils (#4837) ## Issue Addressed Fix a deadlock in the tests that was causing tests on tree-states to run for hours without finishing: https://github.com/sigp/lighthouse/actions/runs/6491194654/job/17628138360. ## Proposed Changes Avoid using a Mutex under the Rayon `par_iter`. Instead, use an `AtomicUsize`. I've run the new version several times in a loop and it hasn't deadlocked (it was deadlocking consistently on tree-states). ## Additional Info The same bug exists in unstable and tree-states, but I'm not sure why it was triggering so consistently on the tree-states branch. --- beacon_node/beacon_chain/src/test_utils.rs | 23 +++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index a77da4c8e53..d9a36cb008f 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -52,6 +52,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt; use std::marker::PhantomData; use std::str::FromStr; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; use store::{config::StoreConfig, HotColdDB, ItemStore, LevelDB, MemoryStore}; @@ -1156,9 +1157,9 @@ where ) -> (Vec>, Vec) { let MakeAttestationOptions { limit, fork } = opts; let committee_count = state.get_committee_count_at_slot(state.slot()).unwrap(); - let attesters = Mutex::new(vec![]); + let num_attesters = AtomicUsize::new(0); - let attestations = state + let (attestations, split_attesters) = state .get_beacon_committees_at_slot(attestation_slot) .expect("should get committees") .iter() @@ -1171,13 +1172,14 @@ where return None; } - let mut attesters = attesters.lock(); if let Some(limit) = limit { - if attesters.len() >= limit { + // This atomics stuff is necessary because we're under a par_iter, + // and Rayon will deadlock if we use a mutex. + if num_attesters.fetch_add(1, Ordering::Relaxed) >= limit { + num_attesters.fetch_sub(1, Ordering::Relaxed); return None; } } - attesters.push(*validator_index); let mut attestation = self .produce_unaggregated_attestation_for_block( @@ -1217,14 +1219,17 @@ where ) .unwrap(); - Some((attestation, subnet_id)) + Some(((attestation, subnet_id), validator_index)) }) - .collect::>() + .unzip::<_, _, Vec<_>, Vec<_>>() }) - .collect::>(); + .unzip::<_, _, Vec<_>, Vec<_>>(); + + // Flatten attesters. + let attesters = split_attesters.into_iter().flatten().collect::>(); - let attesters = attesters.into_inner(); if let Some(limit) = limit { + assert_eq!(limit, num_attesters.load(Ordering::Relaxed)); assert_eq!( limit, attesters.len(), From 5bbeedb5b726f280b05435eecc05fcf95a753027 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Wed, 18 Oct 2023 14:19:41 +0000 Subject: [PATCH 20/21] Reduce nextest threads to 8 (#4846) ## Issue Addressed Fix OOMs caused by too many concurrent tests. The runner machine is currently liable to run `32 * 5 = 160` tests in parallel. If each test uses say 300MB max, this is 48GB of RAM! ## Proposed Changes Reduce the number of threads per runner job to 8. This should cap the memory at 4x lower than the current limit, i.e. around 12GB. If we continue to run out of RAM, we should consider more sophisticated limits. --- .config/nextest.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index b701259fc2a..1ef771b3d94 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -20,7 +20,7 @@ retries = 0 # The number of threads to run tests with. Supported values are either an integer or # the string "num-cpus". Can be overridden through the `--test-threads` option. -test-threads = "num-cpus" +test-threads = 8 # The number of threads required for each test. This is generally used in overrides to # mark certain tests as heavier than others. However, it can also be set as a global parameter. From 98cac2bc6b4c844380e46134f981f1dd802392c0 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Wed, 18 Oct 2023 15:23:31 +0000 Subject: [PATCH 21/21] Deneb review `.github` (CI cleanup) (#4696) ## Issue Addressed Related to https://github.com/sigp/lighthouse/issues/4676. Deneb-specifc CI code to be removed before merging to `unstable`. Dot not merge until we're ready to merge into `unstable`, as we may need to release deneb docker images before merging. Keep in mind that most of the changes in the below PR (to `unstable`) have already been merged to `deneb-free-blobs`, so merging `deneb-free-blobs` into `unstable` would include those changes - it would be ok if the release runners are ready, otherwise we may want to exclude them before merging. - https://github.com/sigp/lighthouse/pull/4592 --- .github/workflows/docker.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8145503e707..007070dbb5b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -5,7 +5,6 @@ on: branches: - unstable - stable - - deneb-free-blobs tags: - v* @@ -41,11 +40,6 @@ jobs: run: | echo "VERSION=latest" >> $GITHUB_ENV echo "VERSION_SUFFIX=-unstable" >> $GITHUB_ENV - - name: Extract version (if deneb) - if: github.event.ref == 'refs/heads/deneb-free-blobs' - run: | - echo "VERSION=deneb" >> $GITHUB_ENV - echo "VERSION_SUFFIX=" >> $GITHUB_ENV - name: Extract version (if tagged release) if: startsWith(github.event.ref, 'refs/tags') run: |