Merge branch 'beacon-api-electra' of https://github.com/sigp/lighthouse into ef-tests-electra

This commit is contained in:
realbigsean
2024-06-25 13:14:24 -07:00
40 changed files with 486 additions and 277 deletions

1
Cargo.lock generated
View File

@@ -7653,6 +7653,7 @@ dependencies = [
"safe_arith", "safe_arith",
"serde", "serde",
"slog", "slog",
"ssz_types",
"strum", "strum",
"tempfile", "tempfile",
"tree_hash", "tree_hash",

View File

@@ -61,10 +61,9 @@ use std::borrow::Cow;
use strum::AsRefStr; use strum::AsRefStr;
use tree_hash::TreeHash; use tree_hash::TreeHash;
use types::{ use types::{
Attestation, AttestationRef, BeaconCommittee, Attestation, AttestationRef, BeaconCommittee, BeaconStateError::NoCommitteeFound, ChainSpec,
BeaconStateError::{self, NoCommitteeFound}, CommitteeIndex, Epoch, EthSpec, Hash256, IndexedAttestation, SelectionProof,
ChainSpec, CommitteeIndex, Epoch, EthSpec, ForkName, Hash256, IndexedAttestation, SignedAggregateAndProof, Slot, SubnetId,
SelectionProof, SignedAggregateAndProof, Slot, SubnetId,
}; };
pub use batch::{batch_verify_aggregated_attestations, batch_verify_unaggregated_attestations}; pub use batch::{batch_verify_aggregated_attestations, batch_verify_unaggregated_attestations};
@@ -266,30 +265,9 @@ pub enum Error {
BeaconChainError(BeaconChainError), BeaconChainError(BeaconChainError),
} }
// TODO(electra) the error conversion changes here are to get a test case to pass
// this could easily be cleaned up
impl From<BeaconChainError> for Error { impl From<BeaconChainError> for Error {
fn from(e: BeaconChainError) -> Self { fn from(e: BeaconChainError) -> Self {
match &e { Self::BeaconChainError(e)
BeaconChainError::BeaconStateError(beacon_state_error) => {
if let BeaconStateError::AggregatorNotInCommittee { aggregator_index } =
beacon_state_error
{
Self::AggregatorNotInCommittee {
aggregator_index: *aggregator_index,
}
} else if let BeaconStateError::InvalidSelectionProof { aggregator_index } =
beacon_state_error
{
Self::InvalidSelectionProof {
aggregator_index: *aggregator_index,
}
} else {
Error::BeaconChainError(e)
}
}
_ => Error::BeaconChainError(e),
}
} }
} }
@@ -1169,7 +1147,7 @@ pub fn verify_propagation_slot_range<S: SlotClock, E: EthSpec>(
let current_fork = let current_fork =
spec.fork_name_at_slot::<E>(slot_clock.now().ok_or(BeaconChainError::UnableToReadSlot)?); spec.fork_name_at_slot::<E>(slot_clock.now().ok_or(BeaconChainError::UnableToReadSlot)?);
let earliest_permissible_slot = if current_fork < ForkName::Deneb { let earliest_permissible_slot = if !current_fork.deneb_enabled() {
one_epoch_prior one_epoch_prior
// EIP-7045 // EIP-7045
} else { } else {
@@ -1414,11 +1392,11 @@ pub fn obtain_indexed_attestation_and_committees_per_slot<T: BeaconChainTypes>(
/// Runs the `map_fn` with the committee and committee count per slot for the given `attestation`. /// Runs the `map_fn` with the committee and committee count per slot for the given `attestation`.
/// ///
/// This function exists in this odd "map" pattern because efficiently obtaining the committees for /// This function exists in this odd "map" pattern because efficiently obtaining the committees for
/// an attestations slot can be complex. It might involve reading straight from the /// an attestation's slot can be complex. It might involve reading straight from the
/// `beacon_chain.shuffling_cache` or it might involve reading it from a state from the DB. Due to /// `beacon_chain.shuffling_cache` or it might involve reading it from a state from the DB. Due to
/// the complexities of `RwLock`s on the shuffling cache, a simple `Cow` isn't suitable here. /// the complexities of `RwLock`s on the shuffling cache, a simple `Cow` isn't suitable here.
/// ///
/// If the committees for an `attestation`'s slot isn't found in the `shuffling_cache`, we will read a state /// If the committees for an `attestation`'s slot aren't found in the `shuffling_cache`, we will read a state
/// from disk and then update the `shuffling_cache`. /// from disk and then update the `shuffling_cache`.
/// ///
/// Committees are sorted by ascending index order 0..committees_per_slot /// Committees are sorted by ascending index order 0..committees_per_slot

View File

@@ -2,7 +2,9 @@
use beacon_chain::block_verification_types::{AsBlock, ExecutedBlock, RpcBlock}; use beacon_chain::block_verification_types::{AsBlock, ExecutedBlock, RpcBlock};
use beacon_chain::{ use beacon_chain::{
test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType}, test_utils::{
test_spec, AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType,
},
AvailabilityProcessingStatus, BeaconChain, BeaconChainTypes, ExecutionPendingBlock, AvailabilityProcessingStatus, BeaconChain, BeaconChainTypes, ExecutionPendingBlock,
}; };
use beacon_chain::{ use beacon_chain::{
@@ -1210,8 +1212,14 @@ async fn block_gossip_verification() {
#[tokio::test] #[tokio::test]
async fn verify_block_for_gossip_slashing_detection() { async fn verify_block_for_gossip_slashing_detection() {
let slasher_dir = tempdir().unwrap(); let slasher_dir = tempdir().unwrap();
let spec = Arc::new(test_spec::<E>());
let slasher = Arc::new( let slasher = Arc::new(
Slasher::open(SlasherConfig::new(slasher_dir.path().into()), test_logger()).unwrap(), Slasher::open(
SlasherConfig::new(slasher_dir.path().into()),
spec,
test_logger(),
)
.unwrap(),
); );
let inner_slasher = slasher.clone(); let inner_slasher = slasher.clone();

View File

@@ -184,8 +184,8 @@ pub fn earliest_attestation_validators<E: EthSpec>(
// Bitfield of validators whose attestations are new/fresh. // Bitfield of validators whose attestations are new/fresh.
let mut new_validators = match attestation.indexed { let mut new_validators = match attestation.indexed {
CompactIndexedAttestation::Base(indexed_att) => indexed_att.aggregation_bits.clone(), CompactIndexedAttestation::Base(indexed_att) => indexed_att.aggregation_bits.clone(),
// TODO(electra) per the comments above, this code path is obsolete post altair fork, so maybe we should just return an empty bitlist here? // This code path is obsolete post altair fork, so we just return an empty bitlist here.
CompactIndexedAttestation::Electra(_) => todo!(), CompactIndexedAttestation::Electra(_) => return BitList::with_capacity(0).unwrap(),
}; };
let state_attestations = if attestation.checkpoint.target_epoch == state.current_epoch() { let state_attestations = if attestation.checkpoint.target_epoch == state.current_epoch() {

View File

@@ -165,22 +165,22 @@ impl<E: EthSpec> CompactIndexedAttestation<E> {
CompactIndexedAttestation::Electra(this), CompactIndexedAttestation::Electra(this),
CompactIndexedAttestation::Electra(other), CompactIndexedAttestation::Electra(other),
) => this.should_aggregate(other), ) => this.should_aggregate(other),
// TODO(electra) is a mix of electra and base compact indexed attestations an edge case we need to deal with?
_ => false, _ => false,
} }
} }
pub fn aggregate(&mut self, other: &Self) -> Option<()> { /// Returns `true` if aggregated, otherwise `false`.
pub fn aggregate(&mut self, other: &Self) -> bool {
match (self, other) { match (self, other) {
(CompactIndexedAttestation::Base(this), CompactIndexedAttestation::Base(other)) => { (CompactIndexedAttestation::Base(this), CompactIndexedAttestation::Base(other)) => {
this.aggregate(other) this.aggregate(other);
true
} }
( (
CompactIndexedAttestation::Electra(this), CompactIndexedAttestation::Electra(this),
CompactIndexedAttestation::Electra(other), CompactIndexedAttestation::Electra(other),
) => this.aggregate_same_committee(other), ) => this.aggregate_same_committee(other),
// TODO(electra) is a mix of electra and base compact indexed attestations an edge case we need to deal with? _ => false,
_ => None,
} }
} }
} }
@@ -192,7 +192,7 @@ impl<E: EthSpec> CompactIndexedAttestationBase<E> {
.is_zero() .is_zero()
} }
pub fn aggregate(&mut self, other: &Self) -> Option<()> { pub fn aggregate(&mut self, other: &Self) {
self.attesting_indices = self self.attesting_indices = self
.attesting_indices .attesting_indices
.drain(..) .drain(..)
@@ -201,8 +201,6 @@ impl<E: EthSpec> CompactIndexedAttestationBase<E> {
.collect(); .collect();
self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits); self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits);
self.signature.add_assign_aggregate(&other.signature); self.signature.add_assign_aggregate(&other.signature);
Some(())
} }
} }
@@ -216,9 +214,10 @@ impl<E: EthSpec> CompactIndexedAttestationElectra<E> {
.is_zero() .is_zero()
} }
pub fn aggregate_same_committee(&mut self, other: &Self) -> Option<()> { /// Returns `true` if aggregated, otherwise `false`.
pub fn aggregate_same_committee(&mut self, other: &Self) -> bool {
if self.committee_bits != other.committee_bits { if self.committee_bits != other.committee_bits {
return None; return false;
} }
self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits); self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits);
self.attesting_indices = self self.attesting_indices = self
@@ -228,7 +227,7 @@ impl<E: EthSpec> CompactIndexedAttestationElectra<E> {
.dedup() .dedup()
.collect(); .collect();
self.signature.add_assign_aggregate(&other.signature); self.signature.add_assign_aggregate(&other.signature);
Some(()) true
} }
pub fn aggregate_with_disjoint_committees(&mut self, other: &Self) -> Option<()> { pub fn aggregate_with_disjoint_committees(&mut self, other: &Self) -> Option<()> {
@@ -318,8 +317,7 @@ impl<E: EthSpec> AttestationMap<E> {
for existing_attestation in attestations.iter_mut() { for existing_attestation in attestations.iter_mut() {
if existing_attestation.should_aggregate(&indexed) { if existing_attestation.should_aggregate(&indexed) {
existing_attestation.aggregate(&indexed); aggregated = existing_attestation.aggregate(&indexed);
aggregated = true;
} else if *existing_attestation == indexed { } else if *existing_attestation == indexed {
aggregated = true; aggregated = true;
} }

View File

@@ -39,7 +39,7 @@ use std::ptr;
use types::{ use types::{
sync_aggregate::Error as SyncAggregateError, typenum::Unsigned, AbstractExecPayload, sync_aggregate::Error as SyncAggregateError, typenum::Unsigned, AbstractExecPayload,
Attestation, AttestationData, AttesterSlashing, BeaconState, BeaconStateError, ChainSpec, Attestation, AttestationData, AttesterSlashing, BeaconState, BeaconStateError, ChainSpec,
Epoch, EthSpec, ForkName, ProposerSlashing, SignedBeaconBlock, SignedBlsToExecutionChange, Epoch, EthSpec, ProposerSlashing, SignedBeaconBlock, SignedBlsToExecutionChange,
SignedVoluntaryExit, Slot, SyncAggregate, SyncCommitteeContribution, Validator, SignedVoluntaryExit, Slot, SyncAggregate, SyncCommitteeContribution, Validator,
}; };
@@ -316,10 +316,10 @@ impl<E: EthSpec> OperationPool<E> {
) )
.inspect(|_| num_curr_valid += 1); .inspect(|_| num_curr_valid += 1);
let curr_epoch_limit = if fork_name < ForkName::Electra { let curr_epoch_limit = if fork_name.electra_enabled() {
E::MaxAttestations::to_usize()
} else {
E::MaxAttestationsElectra::to_usize() E::MaxAttestationsElectra::to_usize()
} else {
E::MaxAttestations::to_usize()
}; };
let prev_epoch_limit = if let BeaconState::Base(base_state) = state { let prev_epoch_limit = if let BeaconState::Base(base_state) = state {
std::cmp::min( std::cmp::min(

View File

@@ -80,7 +80,7 @@ impl<E: EthSpec> ProductionBeaconNode<E> {
let builder = ClientBuilder::new(context.eth_spec_instance.clone()) let builder = ClientBuilder::new(context.eth_spec_instance.clone())
.runtime_context(context) .runtime_context(context)
.chain_spec(spec) .chain_spec(spec.clone())
.beacon_processor(client_config.beacon_processor.clone()) .beacon_processor(client_config.beacon_processor.clone())
.http_api_config(client_config.http_api.clone()) .http_api_config(client_config.http_api.clone())
.disk_store( .disk_store(
@@ -113,8 +113,12 @@ impl<E: EthSpec> ProductionBeaconNode<E> {
_ => {} _ => {}
} }
let slasher = Arc::new( let slasher = Arc::new(
Slasher::open(slasher_config, log.new(slog::o!("service" => "slasher"))) Slasher::open(
.map_err(|e| format!("Slasher open error: {:?}", e))?, slasher_config,
Arc::new(spec),
log.new(slog::o!("service" => "slasher")),
)
.map_err(|e| format!("Slasher open error: {:?}", e))?,
); );
builder.slasher(slasher) builder.slasher(slasher)
} else { } else {

View File

@@ -146,8 +146,19 @@ For more information on historic state storage see the
To manually specify a checkpoint use the following two flags: To manually specify a checkpoint use the following two flags:
* `--checkpoint-state`: accepts an SSZ-encoded `BeaconState` blob * `--checkpoint-state`: accepts an SSZ-encoded `BeaconState` file
* `--checkpoint-block`: accepts an SSZ-encoded `SignedBeaconBlock` blob * `--checkpoint-block`: accepts an SSZ-encoded `SignedBeaconBlock` file
* `--checkpoint-blobs`: accepts an SSZ-encoded `Blobs` file
The command is as following:
```bash
curl -H "Accept: application/octet-stream" "http://localhost:5052/eth/v2/debug/beacon/states/$SLOT" > state.ssz
curl -H "Accept: application/octet-stream" "http://localhost:5052/eth/v2/beacon/blocks/$SLOT" > block.ssz
curl -H "Accept: application/octet-stream" "http://localhost:5052/eth/v1/beacon/blob_sidecars/$SLOT" > blobs.ssz
```
where `$SLOT` is the slot number. It can be specified as `head` or `finalized` as well.
_Both_ the state and block must be provided and the state **must** match the block. The _Both_ the state and block must be provided and the state **must** match the block. The
state may be from the same slot as the block (unadvanced), or advanced to an epoch boundary, state may be from the same slot as the block (unadvanced), or advanced to an epoch boundary,

View File

@@ -16,6 +16,7 @@ validator client or the slasher**.
| Lighthouse version | Release date | Schema version | Downgrade available? | | Lighthouse version | Release date | Schema version | Downgrade available? |
|--------------------|--------------|----------------|----------------------| |--------------------|--------------|----------------|----------------------|
| v5.2.0 | Jun 2024 | v19 | yes before Deneb |
| v5.1.0 | Mar 2024 | v19 | yes before Deneb | | v5.1.0 | Mar 2024 | v19 | yes before Deneb |
| v5.0.0 | Feb 2024 | v19 | yes before Deneb | | v5.0.0 | Feb 2024 | v19 | yes before Deneb |
| v4.6.0 | Dec 2023 | v19 | yes before Deneb | | v4.6.0 | Dec 2023 | v19 | yes before Deneb |

View File

@@ -15,6 +15,7 @@
- [My beacon node logs `WARN Error signalling fork choice waiter`, what should I do?](#bn-fork-choice) - [My beacon node logs `WARN Error signalling fork choice waiter`, what should I do?](#bn-fork-choice)
- [My beacon node logs `ERRO Aggregate attestation queue full`, what should I do?](#bn-queue-full) - [My beacon node logs `ERRO Aggregate attestation queue full`, what should I do?](#bn-queue-full)
- [My beacon node logs `WARN Failed to finalize deposit cache`, what should I do?](#bn-deposit-cache) - [My beacon node logs `WARN Failed to finalize deposit cache`, what should I do?](#bn-deposit-cache)
- [My beacon node logs `WARN Could not verify blob sidecar for gossip`, what does it mean?](#bn-blob)
## [Validator](#validator-1) ## [Validator](#validator-1)
@@ -214,6 +215,16 @@ This suggests that the computer resources are being overwhelmed. It could be due
This is a known [bug](https://github.com/sigp/lighthouse/issues/3707) that will fix by itself. This is a known [bug](https://github.com/sigp/lighthouse/issues/3707) that will fix by itself.
### <a name="bn-blob"></a> My beacon node logs `WARN Could not verify blob sidecar for gossip`, what does it mean?
An example of the full log is shown below:
```text
Jun 07 23:05:12.170 WARN Could not verify blob sidecar for gossip. Ignoring the blob sidecar, commitment: 0xaa97…6f54, index: 1, root: 0x93b8…c47c, slot: 9248017, error: PastFinalizedSlot { blob_slot: Slot(9248017), finalized_slot: Slot(9248032) }, module: network::network_beacon_processor::gossip_methods:720
```
The `PastFinalizedSlot` indicates that the time at which the node received the blob has past the finalization period. This could be due to a peer sending an earlier blob. The log will be gone when Lighthouse eventually drops the peer.
## Validator ## Validator
### <a name="vc-activation"></a> Why does it take so long for a validator to be activated? ### <a name="vc-activation"></a> Why does it take so long for a validator to be activated?
@@ -327,13 +338,24 @@ The first thing is to ensure both consensus and execution clients are synced wit
You can see more information on the [Ethstaker KB](https://ethstaker.gitbook.io/ethstaker-knowledge-base/help/missed-attestations). You can see more information on the [Ethstaker KB](https://ethstaker.gitbook.io/ethstaker-knowledge-base/help/missed-attestations).
Another cause for missing attestations is delays during block processing. When this happens, the debug logs will show (debug logs can be found under `$datadir/beacon/logs`): Another cause for missing attestations is the block arriving late, or there are delays during block processing.
An example of the log: (debug logs can be found under `$datadir/beacon/logs`):
```text ```text
DEBG Delayed head block set_as_head_delay: Some(93.579425ms), imported_delay: Some(1.460405278s), observed_delay: Some(2.540811921s), block_delay: 4.094796624s, slot: 6837344, proposer_index: 211108, block_root: 0x2c52231c0a5a117401f5231585de8aa5dd963bc7cbc00c544e681342eedd1700, service: beacon Delayed head block, set_as_head_time_ms: 27, imported_time_ms: 168, attestable_delay_ms: 4209, available_delay_ms: 4186, execution_time_ms: 201, blob_delay_ms: 3815, observed_delay_ms: 3984, total_delay_ms: 4381, slot: 1886014, proposer_index: 733, block_root: 0xa7390baac88d50f1cbb5ad81691915f6402385a12521a670bbbd4cd5f8bf3934, service: beacon, module: beacon_chain::canonical_head:1441
``` ```
The fields to look for are `imported_delay > 1s` and `observed_delay < 3s`. The `imported_delay` is how long the node took to process the block. The `imported_delay` of larger than 1 second suggests that there is slowness in processing the block. It could be due to high CPU usage, high I/O disk usage or the clients are doing some background maintenance processes. The `observed_delay` is determined mostly by the proposer and partly by your networking setup (e.g., how long it took for the node to receive the block). The `observed_delay` of less than 3 seconds means that the block is not arriving late from the block proposer. Combining the above, this implies that the validator should have been able to attest to the block, but failed due to slowness in the node processing the block. The field to look for is `attestable_delay`, which defines the time when a block is ready for the validator to attest. If the `attestable_delay` is greater than 4s which has past the window of attestation, the attestation wil fail. In the above example, the delay is mostly caused by late block observed by the node, as shown in `observed_delay`. The `observed_delay` is determined mostly by the proposer and partly by your networking setup (e.g., how long it took for the node to receive the block). Ideally, `observed_delay` should be less than 3 seconds. In this example, the validator failed to attest the block due to the block arriving late.
Another example of log:
```
DEBG Delayed head block, set_as_head_time_ms: 22, imported_time_ms: 312, attestable_delay_ms: 7052, available_delay_ms: 6874, execution_time_ms: 4694, blob_delay_ms: 2159, observed_delay_ms: 2179, total_delay_ms: 7209, slot: 1885922, proposer_index: 606896, block_root: 0x9966df24d24e722d7133068186f0caa098428696e9f441ac416d0aca70cc0a23, service: beacon, module: beacon_chain::canonical_head:1441
/159.69.68.247/tcp/9000, service: libp2p, module: lighthouse_network::service:1811
```
In this example, we see that the `execution_time_ms` is 4694ms. The `execution_time_ms` is how long the node took to process the block. The `execution_time_ms` of larger than 1 second suggests that there is slowness in processing the block. If the `execution_time_ms` is high, it could be due to high CPU usage, high I/O disk usage or the clients are doing some background maintenance processes.
### <a name="vc-head-vote"></a> Sometimes I miss the attestation head vote, resulting in penalty. Is this normal? ### <a name="vc-head-vote"></a> Sometimes I miss the attestation head vote, resulting in penalty. Is this normal?
@@ -514,21 +536,23 @@ If you would still like to subscribe to all subnets, you can use the flag `subsc
### <a name="net-quic"></a> How to know how many of my peers are connected via QUIC? ### <a name="net-quic"></a> How to know how many of my peers are connected via QUIC?
With `--metrics` enabled in the beacon node, you can find the number of peers connected via QUIC using: With `--metrics` enabled in the beacon node, the [Grafana Network dashboard](https://github.com/sigp/lighthouse-metrics/blob/master/dashboards/Network.json) displays the connected by transport, which will show the number of peers connected via QUIC.
Alternatively, you can find the number of peers connected via QUIC manually using:
```bash ```bash
curl -s "http://localhost:5054/metrics" | grep libp2p_quic_peers curl -s "http://localhost:5054/metrics" | grep 'transport="quic"'
``` ```
A response example is: A response example is:
```text ```text
# HELP libp2p_quic_peers Count of libp2p peers currently connected via QUIC libp2p_peers_multi{direction="inbound",transport="quic"} 27
# TYPE libp2p_quic_peers gauge libp2p_peers_multi{direction="none",transport="quic"} 0
libp2p_quic_peers 4 libp2p_peers_multi{direction="outbound",transport="quic"} 9
``` ```
which shows that there are 4 peers connected via QUIC. which shows that there are a total of 36 peers connected via QUIC.
## Miscellaneous ## Miscellaneous

View File

@@ -114,13 +114,13 @@ changed after initialization.
* Flag: `--slasher-max-db-size GIGABYTES` * Flag: `--slasher-max-db-size GIGABYTES`
* Argument: maximum size of the database in gigabytes * Argument: maximum size of the database in gigabytes
* Default: 256 GB * Default: 512 GB
Both database backends LMDB and MDBX place a hard limit on the size of the database Both database backends LMDB and MDBX place a hard limit on the size of the database
file. You can use the `--slasher-max-db-size` flag to set this limit. It can be adjusted after file. You can use the `--slasher-max-db-size` flag to set this limit. It can be adjusted after
initialization if the limit is reached. initialization if the limit is reached.
By default the limit is set to accommodate the default history length and around 600K validators (with about 30% headroom) but By default the limit is set to accommodate the default history length and around 1 million validators but
you can set it lower if running with a reduced history length. The space required scales you can set it lower if running with a reduced history length. The space required scales
approximately linearly in validator count and history length, i.e. if you halve either you can halve approximately linearly in validator count and history length, i.e. if you halve either you can halve
the space required. the space required.

View File

@@ -75,7 +75,7 @@ Once you have the slashing protection database from your existing client, you ca
using this command: using this command:
```bash ```bash
lighthouse account validator slashing-protection import <my_interchange.json> lighthouse account validator slashing-protection import filename.json
``` ```
When importing an interchange file, you still need to import the validator keystores themselves When importing an interchange file, you still need to import the validator keystores themselves
@@ -86,7 +86,7 @@ separately, using the instructions for [import validator keys](./mainnet-validat
You can export Lighthouse's database for use with another client with this command: You can export Lighthouse's database for use with another client with this command:
``` ```
lighthouse account validator slashing-protection export <lighthouse_interchange.json> lighthouse account validator slashing-protection export filename.json
``` ```
The validator client needs to be stopped in order to export, to guarantee that the data exported is The validator client needs to be stopped in order to export, to guarantee that the data exported is

View File

@@ -626,6 +626,7 @@ pub fn process_withdrawals<E: EthSpec, Payload: AbstractExecPayload<E>>(
// Update pending partial withdrawals [New in Electra:EIP7251] // Update pending partial withdrawals [New in Electra:EIP7251]
if let Some(partial_withdrawals_count) = partial_withdrawals_count { if let Some(partial_withdrawals_count) = partial_withdrawals_count {
// TODO(electra): Use efficient pop_front after milhouse release https://github.com/sigp/milhouse/pull/38
let new_partial_withdrawals = state let new_partial_withdrawals = state
.pending_partial_withdrawals()? .pending_partial_withdrawals()?
.iter_from(partial_withdrawals_count)? .iter_from(partial_withdrawals_count)?

View File

@@ -48,7 +48,6 @@ pub trait TransformPersist {
pub struct SigVerifiedOp<T: TransformPersist, E: EthSpec> { pub struct SigVerifiedOp<T: TransformPersist, E: EthSpec> {
op: T, op: T,
verified_against: VerifiedAgainst, verified_against: VerifiedAgainst,
//#[ssz(skip_serializing, skip_deserializing)]
_phantom: PhantomData<E>, _phantom: PhantomData<E>,
} }

View File

@@ -33,7 +33,8 @@ use tree_hash_derive::TreeHash;
derive(Debug, PartialEq, TreeHash, Serialize,), derive(Debug, PartialEq, TreeHash, Serialize,),
serde(untagged, bound = "E: EthSpec"), serde(untagged, bound = "E: EthSpec"),
tree_hash(enum_behaviour = "transparent") tree_hash(enum_behaviour = "transparent")
) ),
map_ref_into(AttestationRef)
)] )]
#[derive( #[derive(
arbitrary::Arbitrary, Debug, Clone, PartialEq, Serialize, Deserialize, Encode, TreeHash, arbitrary::Arbitrary, Debug, Clone, PartialEq, Serialize, Deserialize, Encode, TreeHash,
@@ -59,19 +60,17 @@ pub struct AggregateAndProof<E: EthSpec> {
impl<'a, E: EthSpec> AggregateAndProofRef<'a, E> { impl<'a, E: EthSpec> AggregateAndProofRef<'a, E> {
/// Returns `true` if `validator_pubkey` signed over `self.aggregate.data.slot`. /// Returns `true` if `validator_pubkey` signed over `self.aggregate.data.slot`.
pub fn aggregate(self) -> AttestationRef<'a, E> { pub fn aggregate(self) -> AttestationRef<'a, E> {
match self { map_aggregate_and_proof_ref_into_attestation_ref!(&'a _, self, |inner, cons| {
AggregateAndProofRef::Base(a) => AttestationRef::Base(&a.aggregate), cons(&inner.aggregate)
AggregateAndProofRef::Electra(a) => AttestationRef::Electra(&a.aggregate), })
}
} }
} }
impl<E: EthSpec> AggregateAndProof<E> { impl<E: EthSpec> AggregateAndProof<E> {
/// Returns `true` if `validator_pubkey` signed over `self.aggregate.data.slot`. /// Returns `true` if `validator_pubkey` signed over `self.aggregate.data.slot`.
pub fn aggregate(&self) -> AttestationRef<E> { pub fn aggregate<'a>(&'a self) -> AttestationRef<'a, E> {
match self { map_aggregate_and_proof_ref_into_attestation_ref!(&'a _, self.to_ref(), |inner, cons| {
AggregateAndProof::Base(a) => AttestationRef::Base(&a.aggregate), cons(&inner.aggregate)
AggregateAndProof::Electra(a) => AttestationRef::Electra(&a.aggregate), })
}
} }
} }

View File

@@ -1,6 +1,6 @@
use crate::slot_data::SlotData; use crate::slot_data::SlotData;
use crate::Checkpoint;
use crate::{test_utils::TestRandom, Hash256, Slot}; use crate::{test_utils::TestRandom, Hash256, Slot};
use crate::{Checkpoint, ForkName};
use derivative::Derivative; use derivative::Derivative;
use safe_arith::ArithError; use safe_arith::ArithError;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -99,7 +99,7 @@ impl<E: EthSpec> Attestation<E> {
target: Checkpoint, target: Checkpoint,
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
if spec.fork_name_at_slot::<E>(slot) >= ForkName::Electra { if spec.fork_name_at_slot::<E>(slot).electra_enabled() {
let mut committee_bits: BitVector<E::MaxCommitteesPerSlot> = BitVector::default(); let mut committee_bits: BitVector<E::MaxCommitteesPerSlot> = BitVector::default();
committee_bits committee_bits
.set(committee_index as usize, true) .set(committee_index as usize, true)
@@ -277,16 +277,6 @@ impl<'a, E: EthSpec> AttestationRef<'a, E> {
} }
impl<E: EthSpec> AttestationElectra<E> { impl<E: EthSpec> AttestationElectra<E> {
/// Are the aggregation bitfields of these attestations disjoint?
// TODO(electra): check whether the definition from CompactIndexedAttestation::should_aggregate
// is useful where this is used, i.e. only consider attestations disjoint when their committees
// match AND their aggregation bits do not intersect.
pub fn signers_disjoint_from(&self, other: &Self) -> bool {
self.aggregation_bits
.intersection(&other.aggregation_bits)
.is_zero()
}
pub fn committee_index(&self) -> Option<u64> { pub fn committee_index(&self) -> Option<u64> {
self.get_committee_indices().first().cloned() self.get_committee_indices().first().cloned()
} }
@@ -304,7 +294,6 @@ impl<E: EthSpec> AttestationElectra<E> {
/// The aggregation bitfields must be disjoint, and the data must be the same. /// The aggregation bitfields must be disjoint, and the data must be the same.
pub fn aggregate(&mut self, other: &Self) { pub fn aggregate(&mut self, other: &Self) {
debug_assert_eq!(self.data, other.data); debug_assert_eq!(self.data, other.data);
debug_assert!(self.signers_disjoint_from(other));
self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits); self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits);
self.signature.add_assign_aggregate(&other.signature); self.signature.add_assign_aggregate(&other.signature);
} }
@@ -358,19 +347,11 @@ impl<E: EthSpec> AttestationElectra<E> {
} }
impl<E: EthSpec> AttestationBase<E> { impl<E: EthSpec> AttestationBase<E> {
/// Are the aggregation bitfields of these attestations disjoint?
pub fn signers_disjoint_from(&self, other: &Self) -> bool {
self.aggregation_bits
.intersection(&other.aggregation_bits)
.is_zero()
}
/// Aggregate another Attestation into this one. /// Aggregate another Attestation into this one.
/// ///
/// The aggregation bitfields must be disjoint, and the data must be the same. /// The aggregation bitfields must be disjoint, and the data must be the same.
pub fn aggregate(&mut self, other: &Self) { pub fn aggregate(&mut self, other: &Self) {
debug_assert_eq!(self.data, other.data); debug_assert_eq!(self.data, other.data);
debug_assert!(self.signers_disjoint_from(other));
self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits); self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits);
self.signature.add_assign_aggregate(&other.signature); self.signature.add_assign_aggregate(&other.signature);
} }

View File

@@ -118,12 +118,8 @@ impl<E: EthSpec> ExecutionPayloadHeader<E> {
pub fn ssz_max_var_len_for_fork(fork_name: ForkName) -> usize { pub fn ssz_max_var_len_for_fork(fork_name: ForkName) -> usize {
// Matching here in case variable fields are added in future forks. // Matching here in case variable fields are added in future forks.
match fork_name { match fork_name {
ForkName::Base ForkName::Base | ForkName::Altair => 0,
| ForkName::Altair ForkName::Bellatrix | ForkName::Capella | ForkName::Deneb | ForkName::Electra => {
| ForkName::Bellatrix
| ForkName::Capella
| ForkName::Deneb
| ForkName::Electra => {
// Max size of variable length `extra_data` field // Max size of variable length `extra_data` field
E::max_extra_data_bytes() * <u8 as Encode>::ssz_fixed_len() E::max_extra_data_bytes() * <u8 as Encode>::ssz_fixed_len()
} }

View File

@@ -120,6 +120,10 @@ impl ForkName {
} }
} }
pub fn deneb_enabled(self) -> bool {
self >= ForkName::Deneb
}
pub fn electra_enabled(self) -> bool { pub fn electra_enabled(self) -> bool {
self >= ForkName::Electra self >= ForkName::Electra
} }

View File

@@ -240,38 +240,6 @@ mod quoted_variable_list_u64 {
} }
} }
#[derive(Debug, Clone, Encode, Decode, PartialEq)]
#[ssz(enum_behaviour = "union")]
pub enum IndexedAttestationOnDisk<E: EthSpec> {
Base(IndexedAttestationBase<E>),
Electra(IndexedAttestationElectra<E>),
}
#[derive(Debug, Clone, Encode, PartialEq)]
#[ssz(enum_behaviour = "union")]
pub enum IndexedAttestationRefOnDisk<'a, E: EthSpec> {
Base(&'a IndexedAttestationBase<E>),
Electra(&'a IndexedAttestationElectra<E>),
}
impl<'a, E: EthSpec> From<&'a IndexedAttestation<E>> for IndexedAttestationRefOnDisk<'a, E> {
fn from(attestation: &'a IndexedAttestation<E>) -> Self {
match attestation {
IndexedAttestation::Base(attestation) => Self::Base(attestation),
IndexedAttestation::Electra(attestation) => Self::Electra(attestation),
}
}
}
impl<E: EthSpec> From<IndexedAttestationOnDisk<E>> for IndexedAttestation<E> {
fn from(attestation: IndexedAttestationOnDisk<E>) -> Self {
match attestation {
IndexedAttestationOnDisk::Base(attestation) => Self::Base(attestation),
IndexedAttestationOnDisk::Electra(attestation) => Self::Electra(attestation),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -176,27 +176,28 @@ pub use crate::fork_versioned_response::{ForkVersionDeserialize, ForkVersionedRe
pub use crate::graffiti::{Graffiti, GRAFFITI_BYTES_LEN}; pub use crate::graffiti::{Graffiti, GRAFFITI_BYTES_LEN};
pub use crate::historical_batch::HistoricalBatch; pub use crate::historical_batch::HistoricalBatch;
pub use crate::indexed_attestation::{ pub use crate::indexed_attestation::{
IndexedAttestation, IndexedAttestationBase, IndexedAttestationElectra, IndexedAttestation, IndexedAttestationBase, IndexedAttestationElectra, IndexedAttestationRef,
IndexedAttestationOnDisk, IndexedAttestationRef, IndexedAttestationRefOnDisk,
}; };
pub use crate::light_client_bootstrap::{ pub use crate::light_client_bootstrap::{
LightClientBootstrap, LightClientBootstrapAltair, LightClientBootstrapCapella, LightClientBootstrap, LightClientBootstrapAltair, LightClientBootstrapCapella,
LightClientBootstrapDeneb, LightClientBootstrapDeneb, LightClientBootstrapElectra,
}; };
pub use crate::light_client_finality_update::{ pub use crate::light_client_finality_update::{
LightClientFinalityUpdate, LightClientFinalityUpdateAltair, LightClientFinalityUpdateCapella, LightClientFinalityUpdate, LightClientFinalityUpdateAltair, LightClientFinalityUpdateCapella,
LightClientFinalityUpdateDeneb, LightClientFinalityUpdateDeneb, LightClientFinalityUpdateElectra,
}; };
pub use crate::light_client_header::{ pub use crate::light_client_header::{
LightClientHeader, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeader, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb,
LightClientHeaderElectra,
}; };
pub use crate::light_client_optimistic_update::{ pub use crate::light_client_optimistic_update::{
LightClientOptimisticUpdate, LightClientOptimisticUpdateAltair, LightClientOptimisticUpdate, LightClientOptimisticUpdateAltair,
LightClientOptimisticUpdateCapella, LightClientOptimisticUpdateDeneb, LightClientOptimisticUpdateCapella, LightClientOptimisticUpdateDeneb,
LightClientOptimisticUpdateElectra,
}; };
pub use crate::light_client_update::{ pub use crate::light_client_update::{
Error as LightClientError, LightClientUpdate, LightClientUpdateAltair, Error as LightClientError, LightClientUpdate, LightClientUpdateAltair,
LightClientUpdateCapella, LightClientUpdateDeneb, LightClientUpdateCapella, LightClientUpdateDeneb, LightClientUpdateElectra,
}; };
pub use crate::participation_flags::ParticipationFlags; pub use crate::participation_flags::ParticipationFlags;
pub use crate::participation_list::ParticipationList; pub use crate::participation_list::ParticipationList;

View File

@@ -1,7 +1,8 @@
use crate::{ use crate::{
light_client_update::*, test_utils::TestRandom, BeaconState, ChainSpec, EthSpec, FixedVector, light_client_update::*, test_utils::TestRandom, BeaconState, ChainSpec, EthSpec, FixedVector,
ForkName, ForkVersionDeserialize, Hash256, LightClientHeader, LightClientHeaderAltair, ForkName, ForkVersionDeserialize, Hash256, LightClientHeader, LightClientHeaderAltair,
LightClientHeaderCapella, LightClientHeaderDeneb, SignedBeaconBlock, Slot, SyncCommittee, LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderElectra, SignedBeaconBlock,
Slot, SyncCommittee,
}; };
use derivative::Derivative; use derivative::Derivative;
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
@@ -16,7 +17,7 @@ use tree_hash_derive::TreeHash;
/// A LightClientBootstrap is the initializer we send over to light_client nodes /// A LightClientBootstrap is the initializer we send over to light_client nodes
/// that are trying to generate their basic storage when booting up. /// that are trying to generate their basic storage when booting up.
#[superstruct( #[superstruct(
variants(Altair, Capella, Deneb), variants(Altair, Capella, Deneb, Electra),
variant_attributes( variant_attributes(
derive( derive(
Debug, Debug,
@@ -51,6 +52,8 @@ pub struct LightClientBootstrap<E: EthSpec> {
pub header: LightClientHeaderCapella<E>, pub header: LightClientHeaderCapella<E>,
#[superstruct(only(Deneb), partial_getter(rename = "header_deneb"))] #[superstruct(only(Deneb), partial_getter(rename = "header_deneb"))]
pub header: LightClientHeaderDeneb<E>, pub header: LightClientHeaderDeneb<E>,
#[superstruct(only(Electra), partial_getter(rename = "header_electra"))]
pub header: LightClientHeaderElectra<E>,
/// The `SyncCommittee` used in the requested period. /// The `SyncCommittee` used in the requested period.
pub current_sync_committee: Arc<SyncCommittee<E>>, pub current_sync_committee: Arc<SyncCommittee<E>>,
/// Merkle proof for sync committee /// Merkle proof for sync committee
@@ -66,6 +69,7 @@ impl<E: EthSpec> LightClientBootstrap<E> {
Self::Altair(_) => func(ForkName::Altair), Self::Altair(_) => func(ForkName::Altair),
Self::Capella(_) => func(ForkName::Capella), Self::Capella(_) => func(ForkName::Capella),
Self::Deneb(_) => func(ForkName::Deneb), Self::Deneb(_) => func(ForkName::Deneb),
Self::Electra(_) => func(ForkName::Electra),
} }
} }
@@ -82,9 +86,8 @@ impl<E: EthSpec> LightClientBootstrap<E> {
Self::Altair(LightClientBootstrapAltair::from_ssz_bytes(bytes)?) Self::Altair(LightClientBootstrapAltair::from_ssz_bytes(bytes)?)
} }
ForkName::Capella => Self::Capella(LightClientBootstrapCapella::from_ssz_bytes(bytes)?), ForkName::Capella => Self::Capella(LightClientBootstrapCapella::from_ssz_bytes(bytes)?),
ForkName::Deneb | ForkName::Electra => { ForkName::Deneb => Self::Deneb(LightClientBootstrapDeneb::from_ssz_bytes(bytes)?),
Self::Deneb(LightClientBootstrapDeneb::from_ssz_bytes(bytes)?) ForkName::Electra => Self::Electra(LightClientBootstrapElectra::from_ssz_bytes(bytes)?),
}
ForkName::Base => { ForkName::Base => {
return Err(ssz::DecodeError::BytesInvalid(format!( return Err(ssz::DecodeError::BytesInvalid(format!(
"LightClientBootstrap decoding for {fork_name} not implemented" "LightClientBootstrap decoding for {fork_name} not implemented"
@@ -97,18 +100,16 @@ impl<E: EthSpec> LightClientBootstrap<E> {
#[allow(clippy::arithmetic_side_effects)] #[allow(clippy::arithmetic_side_effects)]
pub fn ssz_max_len_for_fork(fork_name: ForkName) -> usize { pub fn ssz_max_len_for_fork(fork_name: ForkName) -> usize {
// TODO(electra): review electra changes let fixed_len = match fork_name {
match fork_name {
ForkName::Base => 0, ForkName::Base => 0,
ForkName::Altair ForkName::Altair | ForkName::Bellatrix => {
| ForkName::Bellatrix
| ForkName::Capella
| ForkName::Deneb
| ForkName::Electra => {
<LightClientBootstrapAltair<E> as Encode>::ssz_fixed_len() <LightClientBootstrapAltair<E> as Encode>::ssz_fixed_len()
+ LightClientHeader::<E>::ssz_max_var_len_for_fork(fork_name)
} }
} ForkName::Capella => <LightClientBootstrapCapella<E> as Encode>::ssz_fixed_len(),
ForkName::Deneb => <LightClientBootstrapDeneb<E> as Encode>::ssz_fixed_len(),
ForkName::Electra => <LightClientBootstrapElectra<E> as Encode>::ssz_fixed_len(),
};
fixed_len + LightClientHeader::<E>::ssz_max_var_len_for_fork(fork_name)
} }
pub fn from_beacon_state( pub fn from_beacon_state(
@@ -138,11 +139,16 @@ impl<E: EthSpec> LightClientBootstrap<E> {
current_sync_committee, current_sync_committee,
current_sync_committee_branch, current_sync_committee_branch,
}), }),
ForkName::Deneb | ForkName::Electra => Self::Deneb(LightClientBootstrapDeneb { ForkName::Deneb => Self::Deneb(LightClientBootstrapDeneb {
header: LightClientHeaderDeneb::block_to_light_client_header(block)?, header: LightClientHeaderDeneb::block_to_light_client_header(block)?,
current_sync_committee, current_sync_committee,
current_sync_committee_branch, current_sync_committee_branch,
}), }),
ForkName::Electra => Self::Electra(LightClientBootstrapElectra {
header: LightClientHeaderElectra::block_to_light_client_header(block)?,
current_sync_committee,
current_sync_committee_branch,
}),
}; };
Ok(light_client_bootstrap) Ok(light_client_bootstrap)

View File

@@ -2,7 +2,8 @@ use super::{EthSpec, FixedVector, Hash256, LightClientHeader, Slot, SyncAggregat
use crate::ChainSpec; use crate::ChainSpec;
use crate::{ use crate::{
light_client_update::*, test_utils::TestRandom, ForkName, ForkVersionDeserialize, light_client_update::*, test_utils::TestRandom, ForkName, ForkVersionDeserialize,
LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, SignedBeaconBlock, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb,
LightClientHeaderElectra, SignedBeaconBlock,
}; };
use derivative::Derivative; use derivative::Derivative;
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
@@ -15,7 +16,7 @@ use test_random_derive::TestRandom;
use tree_hash_derive::TreeHash; use tree_hash_derive::TreeHash;
#[superstruct( #[superstruct(
variants(Altair, Capella, Deneb), variants(Altair, Capella, Deneb, Electra),
variant_attributes( variant_attributes(
derive( derive(
Debug, Debug,
@@ -50,6 +51,8 @@ pub struct LightClientFinalityUpdate<E: EthSpec> {
pub attested_header: LightClientHeaderCapella<E>, pub attested_header: LightClientHeaderCapella<E>,
#[superstruct(only(Deneb), partial_getter(rename = "attested_header_deneb"))] #[superstruct(only(Deneb), partial_getter(rename = "attested_header_deneb"))]
pub attested_header: LightClientHeaderDeneb<E>, pub attested_header: LightClientHeaderDeneb<E>,
#[superstruct(only(Electra), partial_getter(rename = "attested_header_electra"))]
pub attested_header: LightClientHeaderElectra<E>,
/// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch).
#[superstruct(only(Altair), partial_getter(rename = "finalized_header_altair"))] #[superstruct(only(Altair), partial_getter(rename = "finalized_header_altair"))]
pub finalized_header: LightClientHeaderAltair<E>, pub finalized_header: LightClientHeaderAltair<E>,
@@ -57,6 +60,8 @@ pub struct LightClientFinalityUpdate<E: EthSpec> {
pub finalized_header: LightClientHeaderCapella<E>, pub finalized_header: LightClientHeaderCapella<E>,
#[superstruct(only(Deneb), partial_getter(rename = "finalized_header_deneb"))] #[superstruct(only(Deneb), partial_getter(rename = "finalized_header_deneb"))]
pub finalized_header: LightClientHeaderDeneb<E>, pub finalized_header: LightClientHeaderDeneb<E>,
#[superstruct(only(Electra), partial_getter(rename = "finalized_header_electra"))]
pub finalized_header: LightClientHeaderElectra<E>,
/// Merkle proof attesting finalized header. /// Merkle proof attesting finalized header.
#[test_random(default)] #[test_random(default)]
pub finality_branch: FixedVector<Hash256, FinalizedRootProofLen>, pub finality_branch: FixedVector<Hash256, FinalizedRootProofLen>,
@@ -80,7 +85,7 @@ impl<E: EthSpec> LightClientFinalityUpdate<E> {
.map_err(|_| Error::InconsistentFork)? .map_err(|_| Error::InconsistentFork)?
{ {
ForkName::Altair | ForkName::Bellatrix => { ForkName::Altair | ForkName::Bellatrix => {
let finality_update = LightClientFinalityUpdateAltair { Self::Altair(LightClientFinalityUpdateAltair {
attested_header: LightClientHeaderAltair::block_to_light_client_header( attested_header: LightClientHeaderAltair::block_to_light_client_header(
attested_block, attested_block,
)?, )?,
@@ -90,37 +95,42 @@ impl<E: EthSpec> LightClientFinalityUpdate<E> {
finality_branch, finality_branch,
sync_aggregate, sync_aggregate,
signature_slot, signature_slot,
}; })
Self::Altair(finality_update)
}
ForkName::Capella => {
let finality_update = LightClientFinalityUpdateCapella {
attested_header: LightClientHeaderCapella::block_to_light_client_header(
attested_block,
)?,
finalized_header: LightClientHeaderCapella::block_to_light_client_header(
finalized_block,
)?,
finality_branch,
sync_aggregate,
signature_slot,
};
Self::Capella(finality_update)
}
ForkName::Deneb | ForkName::Electra => {
let finality_update = LightClientFinalityUpdateDeneb {
attested_header: LightClientHeaderDeneb::block_to_light_client_header(
attested_block,
)?,
finalized_header: LightClientHeaderDeneb::block_to_light_client_header(
finalized_block,
)?,
finality_branch,
sync_aggregate,
signature_slot,
};
Self::Deneb(finality_update)
} }
ForkName::Capella => Self::Capella(LightClientFinalityUpdateCapella {
attested_header: LightClientHeaderCapella::block_to_light_client_header(
attested_block,
)?,
finalized_header: LightClientHeaderCapella::block_to_light_client_header(
finalized_block,
)?,
finality_branch,
sync_aggregate,
signature_slot,
}),
ForkName::Deneb => Self::Deneb(LightClientFinalityUpdateDeneb {
attested_header: LightClientHeaderDeneb::block_to_light_client_header(
attested_block,
)?,
finalized_header: LightClientHeaderDeneb::block_to_light_client_header(
finalized_block,
)?,
finality_branch,
sync_aggregate,
signature_slot,
}),
ForkName::Electra => Self::Electra(LightClientFinalityUpdateElectra {
attested_header: LightClientHeaderElectra::block_to_light_client_header(
attested_block,
)?,
finalized_header: LightClientHeaderElectra::block_to_light_client_header(
finalized_block,
)?,
finality_branch,
sync_aggregate,
signature_slot,
}),
ForkName::Base => return Err(Error::AltairForkNotActive), ForkName::Base => return Err(Error::AltairForkNotActive),
}; };
@@ -135,6 +145,7 @@ impl<E: EthSpec> LightClientFinalityUpdate<E> {
Self::Altair(_) => func(ForkName::Altair), Self::Altair(_) => func(ForkName::Altair),
Self::Capella(_) => func(ForkName::Capella), Self::Capella(_) => func(ForkName::Capella),
Self::Deneb(_) => func(ForkName::Deneb), Self::Deneb(_) => func(ForkName::Deneb),
Self::Electra(_) => func(ForkName::Electra),
} }
} }
@@ -153,8 +164,9 @@ impl<E: EthSpec> LightClientFinalityUpdate<E> {
ForkName::Capella => { ForkName::Capella => {
Self::Capella(LightClientFinalityUpdateCapella::from_ssz_bytes(bytes)?) Self::Capella(LightClientFinalityUpdateCapella::from_ssz_bytes(bytes)?)
} }
ForkName::Deneb | ForkName::Electra => { ForkName::Deneb => Self::Deneb(LightClientFinalityUpdateDeneb::from_ssz_bytes(bytes)?),
Self::Deneb(LightClientFinalityUpdateDeneb::from_ssz_bytes(bytes)?) ForkName::Electra => {
Self::Electra(LightClientFinalityUpdateElectra::from_ssz_bytes(bytes)?)
} }
ForkName::Base => { ForkName::Base => {
return Err(ssz::DecodeError::BytesInvalid(format!( return Err(ssz::DecodeError::BytesInvalid(format!(
@@ -168,18 +180,17 @@ impl<E: EthSpec> LightClientFinalityUpdate<E> {
#[allow(clippy::arithmetic_side_effects)] #[allow(clippy::arithmetic_side_effects)]
pub fn ssz_max_len_for_fork(fork_name: ForkName) -> usize { pub fn ssz_max_len_for_fork(fork_name: ForkName) -> usize {
// TODO(electra): review electra changes let fixed_size = match fork_name {
match fork_name {
ForkName::Base => 0, ForkName::Base => 0,
ForkName::Altair ForkName::Altair | ForkName::Bellatrix => {
| ForkName::Bellatrix
| ForkName::Capella
| ForkName::Deneb
| ForkName::Electra => {
<LightClientFinalityUpdateAltair<E> as Encode>::ssz_fixed_len() <LightClientFinalityUpdateAltair<E> as Encode>::ssz_fixed_len()
+ 2 * LightClientHeader::<E>::ssz_max_var_len_for_fork(fork_name)
} }
} ForkName::Capella => <LightClientFinalityUpdateCapella<E> as Encode>::ssz_fixed_len(),
ForkName::Deneb => <LightClientFinalityUpdateDeneb<E> as Encode>::ssz_fixed_len(),
ForkName::Electra => <LightClientFinalityUpdateElectra<E> as Encode>::ssz_fixed_len(),
};
// `2 *` because there are two headers in the update
fixed_size + 2 * LightClientHeader::<E>::ssz_max_var_len_for_fork(fork_name)
} }
} }

View File

@@ -4,7 +4,7 @@ use crate::ForkVersionDeserialize;
use crate::{light_client_update::*, BeaconBlockBody}; use crate::{light_client_update::*, BeaconBlockBody};
use crate::{ use crate::{
test_utils::TestRandom, EthSpec, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, test_utils::TestRandom, EthSpec, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb,
FixedVector, Hash256, SignedBeaconBlock, ExecutionPayloadHeaderElectra, FixedVector, Hash256, SignedBeaconBlock,
}; };
use crate::{BeaconBlockHeader, ExecutionPayloadHeader}; use crate::{BeaconBlockHeader, ExecutionPayloadHeader};
use derivative::Derivative; use derivative::Derivative;
@@ -17,7 +17,7 @@ use test_random_derive::TestRandom;
use tree_hash_derive::TreeHash; use tree_hash_derive::TreeHash;
#[superstruct( #[superstruct(
variants(Altair, Capella, Deneb), variants(Altair, Capella, Deneb, Electra),
variant_attributes( variant_attributes(
derive( derive(
Debug, Debug,
@@ -54,8 +54,13 @@ pub struct LightClientHeader<E: EthSpec> {
pub execution: ExecutionPayloadHeaderCapella<E>, pub execution: ExecutionPayloadHeaderCapella<E>,
#[superstruct(only(Deneb), partial_getter(rename = "execution_payload_header_deneb"))] #[superstruct(only(Deneb), partial_getter(rename = "execution_payload_header_deneb"))]
pub execution: ExecutionPayloadHeaderDeneb<E>, pub execution: ExecutionPayloadHeaderDeneb<E>,
#[superstruct(
only(Electra),
partial_getter(rename = "execution_payload_header_electra")
)]
pub execution: ExecutionPayloadHeaderElectra<E>,
#[superstruct(only(Capella, Deneb))] #[superstruct(only(Capella, Deneb, Electra))]
pub execution_branch: FixedVector<Hash256, ExecutionPayloadProofLen>, pub execution_branch: FixedVector<Hash256, ExecutionPayloadProofLen>,
#[ssz(skip_serializing, skip_deserializing)] #[ssz(skip_serializing, skip_deserializing)]
@@ -81,9 +86,12 @@ impl<E: EthSpec> LightClientHeader<E> {
ForkName::Capella => LightClientHeader::Capella( ForkName::Capella => LightClientHeader::Capella(
LightClientHeaderCapella::block_to_light_client_header(block)?, LightClientHeaderCapella::block_to_light_client_header(block)?,
), ),
ForkName::Deneb | ForkName::Electra => LightClientHeader::Deneb( ForkName::Deneb => LightClientHeader::Deneb(
LightClientHeaderDeneb::block_to_light_client_header(block)?, LightClientHeaderDeneb::block_to_light_client_header(block)?,
), ),
ForkName::Electra => LightClientHeader::Electra(
LightClientHeaderElectra::block_to_light_client_header(block)?,
),
}; };
Ok(header) Ok(header)
} }
@@ -96,9 +104,12 @@ impl<E: EthSpec> LightClientHeader<E> {
ForkName::Capella => { ForkName::Capella => {
LightClientHeader::Capella(LightClientHeaderCapella::from_ssz_bytes(bytes)?) LightClientHeader::Capella(LightClientHeaderCapella::from_ssz_bytes(bytes)?)
} }
ForkName::Deneb | ForkName::Electra => { ForkName::Deneb => {
LightClientHeader::Deneb(LightClientHeaderDeneb::from_ssz_bytes(bytes)?) LightClientHeader::Deneb(LightClientHeaderDeneb::from_ssz_bytes(bytes)?)
} }
ForkName::Electra => {
LightClientHeader::Electra(LightClientHeaderElectra::from_ssz_bytes(bytes)?)
}
ForkName::Base => { ForkName::Base => {
return Err(ssz::DecodeError::BytesInvalid(format!( return Err(ssz::DecodeError::BytesInvalid(format!(
"LightClientHeader decoding for {fork_name} not implemented" "LightClientHeader decoding for {fork_name} not implemented"
@@ -192,6 +203,34 @@ impl<E: EthSpec> LightClientHeaderDeneb<E> {
} }
} }
impl<E: EthSpec> LightClientHeaderElectra<E> {
pub fn block_to_light_client_header(block: &SignedBeaconBlock<E>) -> Result<Self, Error> {
let payload = block
.message()
.execution_payload()?
.execution_payload_electra()?;
let header = ExecutionPayloadHeaderElectra::from(payload);
let beacon_block_body = BeaconBlockBody::from(
block
.message()
.body_electra()
.map_err(|_| Error::BeaconBlockBodyError)?
.to_owned(),
);
let execution_branch =
beacon_block_body.block_body_merkle_proof(EXECUTION_PAYLOAD_INDEX)?;
Ok(LightClientHeaderElectra {
beacon: block.message().block_header(),
execution: header,
execution_branch: FixedVector::new(execution_branch)?,
_phantom_data: PhantomData,
})
}
}
impl<E: EthSpec> ForkVersionDeserialize for LightClientHeader<E> { impl<E: EthSpec> ForkVersionDeserialize for LightClientHeader<E> {
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value, value: serde_json::value::Value,
@@ -204,9 +243,12 @@ impl<E: EthSpec> ForkVersionDeserialize for LightClientHeader<E> {
ForkName::Capella => serde_json::from_value(value) ForkName::Capella => serde_json::from_value(value)
.map(|light_client_header| Self::Capella(light_client_header)) .map(|light_client_header| Self::Capella(light_client_header))
.map_err(serde::de::Error::custom), .map_err(serde::de::Error::custom),
ForkName::Deneb | ForkName::Electra => serde_json::from_value(value) ForkName::Deneb => serde_json::from_value(value)
.map(|light_client_header| Self::Deneb(light_client_header)) .map(|light_client_header| Self::Deneb(light_client_header))
.map_err(serde::de::Error::custom), .map_err(serde::de::Error::custom),
ForkName::Electra => serde_json::from_value(value)
.map(|light_client_header| Self::Electra(light_client_header))
.map_err(serde::de::Error::custom),
ForkName::Base => Err(serde::de::Error::custom(format!( ForkName::Base => Err(serde::de::Error::custom(format!(
"LightClientHeader deserialization for {fork_name} not implemented" "LightClientHeader deserialization for {fork_name} not implemented"
))), ))),

View File

@@ -2,7 +2,7 @@ use super::{EthSpec, ForkName, ForkVersionDeserialize, LightClientHeader, Slot,
use crate::test_utils::TestRandom; use crate::test_utils::TestRandom;
use crate::{ use crate::{
light_client_update::*, ChainSpec, LightClientHeaderAltair, LightClientHeaderCapella, light_client_update::*, ChainSpec, LightClientHeaderAltair, LightClientHeaderCapella,
LightClientHeaderDeneb, SignedBeaconBlock, LightClientHeaderDeneb, LightClientHeaderElectra, SignedBeaconBlock,
}; };
use derivative::Derivative; use derivative::Derivative;
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
@@ -18,7 +18,7 @@ use tree_hash_derive::TreeHash;
/// A LightClientOptimisticUpdate is the update we send on each slot, /// A LightClientOptimisticUpdate is the update we send on each slot,
/// it is based off the current unfinalized epoch is verified only against BLS signature. /// it is based off the current unfinalized epoch is verified only against BLS signature.
#[superstruct( #[superstruct(
variants(Altair, Capella, Deneb), variants(Altair, Capella, Deneb, Electra),
variant_attributes( variant_attributes(
derive( derive(
Debug, Debug,
@@ -53,6 +53,8 @@ pub struct LightClientOptimisticUpdate<E: EthSpec> {
pub attested_header: LightClientHeaderCapella<E>, pub attested_header: LightClientHeaderCapella<E>,
#[superstruct(only(Deneb), partial_getter(rename = "attested_header_deneb"))] #[superstruct(only(Deneb), partial_getter(rename = "attested_header_deneb"))]
pub attested_header: LightClientHeaderDeneb<E>, pub attested_header: LightClientHeaderDeneb<E>,
#[superstruct(only(Electra), partial_getter(rename = "attested_header_electra"))]
pub attested_header: LightClientHeaderElectra<E>,
/// current sync aggregate /// current sync aggregate
pub sync_aggregate: SyncAggregate<E>, pub sync_aggregate: SyncAggregate<E>,
/// Slot of the sync aggregated signature /// Slot of the sync aggregated signature
@@ -86,13 +88,20 @@ impl<E: EthSpec> LightClientOptimisticUpdate<E> {
sync_aggregate, sync_aggregate,
signature_slot, signature_slot,
}), }),
ForkName::Deneb | ForkName::Electra => Self::Deneb(LightClientOptimisticUpdateDeneb { ForkName::Deneb => Self::Deneb(LightClientOptimisticUpdateDeneb {
attested_header: LightClientHeaderDeneb::block_to_light_client_header( attested_header: LightClientHeaderDeneb::block_to_light_client_header(
attested_block, attested_block,
)?, )?,
sync_aggregate, sync_aggregate,
signature_slot, signature_slot,
}), }),
ForkName::Electra => Self::Electra(LightClientOptimisticUpdateElectra {
attested_header: LightClientHeaderElectra::block_to_light_client_header(
attested_block,
)?,
sync_aggregate,
signature_slot,
}),
ForkName::Base => return Err(Error::AltairForkNotActive), ForkName::Base => return Err(Error::AltairForkNotActive),
}; };
@@ -107,6 +116,7 @@ impl<E: EthSpec> LightClientOptimisticUpdate<E> {
Self::Altair(_) => func(ForkName::Altair), Self::Altair(_) => func(ForkName::Altair),
Self::Capella(_) => func(ForkName::Capella), Self::Capella(_) => func(ForkName::Capella),
Self::Deneb(_) => func(ForkName::Deneb), Self::Deneb(_) => func(ForkName::Deneb),
Self::Electra(_) => func(ForkName::Electra),
} }
} }
@@ -139,9 +149,12 @@ impl<E: EthSpec> LightClientOptimisticUpdate<E> {
ForkName::Capella => { ForkName::Capella => {
Self::Capella(LightClientOptimisticUpdateCapella::from_ssz_bytes(bytes)?) Self::Capella(LightClientOptimisticUpdateCapella::from_ssz_bytes(bytes)?)
} }
ForkName::Deneb | ForkName::Electra => { ForkName::Deneb => {
Self::Deneb(LightClientOptimisticUpdateDeneb::from_ssz_bytes(bytes)?) Self::Deneb(LightClientOptimisticUpdateDeneb::from_ssz_bytes(bytes)?)
} }
ForkName::Electra => {
Self::Electra(LightClientOptimisticUpdateElectra::from_ssz_bytes(bytes)?)
}
ForkName::Base => { ForkName::Base => {
return Err(ssz::DecodeError::BytesInvalid(format!( return Err(ssz::DecodeError::BytesInvalid(format!(
"LightClientOptimisticUpdate decoding for {fork_name} not implemented" "LightClientOptimisticUpdate decoding for {fork_name} not implemented"
@@ -154,18 +167,16 @@ impl<E: EthSpec> LightClientOptimisticUpdate<E> {
#[allow(clippy::arithmetic_side_effects)] #[allow(clippy::arithmetic_side_effects)]
pub fn ssz_max_len_for_fork(fork_name: ForkName) -> usize { pub fn ssz_max_len_for_fork(fork_name: ForkName) -> usize {
// TODO(electra): review electra changes let fixed_len = match fork_name {
match fork_name {
ForkName::Base => 0, ForkName::Base => 0,
ForkName::Altair ForkName::Altair | ForkName::Bellatrix => {
| ForkName::Bellatrix
| ForkName::Capella
| ForkName::Deneb
| ForkName::Electra => {
<LightClientOptimisticUpdateAltair<E> as Encode>::ssz_fixed_len() <LightClientOptimisticUpdateAltair<E> as Encode>::ssz_fixed_len()
+ LightClientHeader::<E>::ssz_max_var_len_for_fork(fork_name)
} }
} ForkName::Capella => <LightClientOptimisticUpdateCapella<E> as Encode>::ssz_fixed_len(),
ForkName::Deneb => <LightClientOptimisticUpdateDeneb<E> as Encode>::ssz_fixed_len(),
ForkName::Electra => <LightClientOptimisticUpdateElectra<E> as Encode>::ssz_fixed_len(),
};
fixed_len + LightClientHeader::<E>::ssz_max_var_len_for_fork(fork_name)
} }
} }

View File

@@ -1,4 +1,5 @@
use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee}; use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee};
use crate::light_client_header::LightClientHeaderElectra;
use crate::{ use crate::{
beacon_state, test_utils::TestRandom, BeaconBlock, BeaconBlockHeader, BeaconState, ChainSpec, beacon_state, test_utils::TestRandom, BeaconBlock, BeaconBlockHeader, BeaconState, ChainSpec,
ForkName, ForkVersionDeserialize, LightClientHeaderAltair, LightClientHeaderCapella, ForkName, ForkVersionDeserialize, LightClientHeaderAltair, LightClientHeaderCapella,
@@ -76,7 +77,7 @@ impl From<milhouse::Error> for Error {
/// or to sync up to the last committee period, we need to have one ready for each ALTAIR period /// or to sync up to the last committee period, we need to have one ready for each ALTAIR period
/// we go over, note: there is no need to keep all of the updates from [ALTAIR_PERIOD, CURRENT_PERIOD]. /// we go over, note: there is no need to keep all of the updates from [ALTAIR_PERIOD, CURRENT_PERIOD].
#[superstruct( #[superstruct(
variants(Altair, Capella, Deneb), variants(Altair, Capella, Deneb, Electra),
variant_attributes( variant_attributes(
derive( derive(
Debug, Debug,
@@ -111,6 +112,8 @@ pub struct LightClientUpdate<E: EthSpec> {
pub attested_header: LightClientHeaderCapella<E>, pub attested_header: LightClientHeaderCapella<E>,
#[superstruct(only(Deneb), partial_getter(rename = "attested_header_deneb"))] #[superstruct(only(Deneb), partial_getter(rename = "attested_header_deneb"))]
pub attested_header: LightClientHeaderDeneb<E>, pub attested_header: LightClientHeaderDeneb<E>,
#[superstruct(only(Electra), partial_getter(rename = "attested_header_electra"))]
pub attested_header: LightClientHeaderElectra<E>,
/// The `SyncCommittee` used in the next period. /// The `SyncCommittee` used in the next period.
pub next_sync_committee: Arc<SyncCommittee<E>>, pub next_sync_committee: Arc<SyncCommittee<E>>,
/// Merkle proof for next sync committee /// Merkle proof for next sync committee
@@ -122,6 +125,8 @@ pub struct LightClientUpdate<E: EthSpec> {
pub finalized_header: LightClientHeaderCapella<E>, pub finalized_header: LightClientHeaderCapella<E>,
#[superstruct(only(Deneb), partial_getter(rename = "finalized_header_deneb"))] #[superstruct(only(Deneb), partial_getter(rename = "finalized_header_deneb"))]
pub finalized_header: LightClientHeaderDeneb<E>, pub finalized_header: LightClientHeaderDeneb<E>,
#[superstruct(only(Electra), partial_getter(rename = "finalized_header_electra"))]
pub finalized_header: LightClientHeaderElectra<E>,
/// Merkle proof attesting finalized header. /// Merkle proof attesting finalized header.
pub finality_branch: FixedVector<Hash256, FinalizedRootProofLen>, pub finality_branch: FixedVector<Hash256, FinalizedRootProofLen>,
/// current sync aggreggate /// current sync aggreggate
@@ -221,7 +226,7 @@ impl<E: EthSpec> LightClientUpdate<E> {
signature_slot: block.slot(), signature_slot: block.slot(),
}) })
} }
ForkName::Deneb | ForkName::Electra => { ForkName::Deneb => {
let attested_header = let attested_header =
LightClientHeaderDeneb::block_to_light_client_header(attested_block)?; LightClientHeaderDeneb::block_to_light_client_header(attested_block)?;
let finalized_header = let finalized_header =
@@ -236,6 +241,23 @@ impl<E: EthSpec> LightClientUpdate<E> {
signature_slot: block.slot(), signature_slot: block.slot(),
}) })
} }
ForkName::Electra => {
let attested_header =
LightClientHeaderElectra::block_to_light_client_header(attested_block)?;
let finalized_header =
LightClientHeaderElectra::block_to_light_client_header(finalized_block)?;
Self::Electra(LightClientUpdateElectra {
attested_header,
next_sync_committee: attested_state.next_sync_committee()?.clone(),
next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?,
finalized_header,
finality_branch: FixedVector::new(finality_branch)?,
sync_aggregate: sync_aggregate.clone(),
signature_slot: block.slot(),
})
} // To add a new fork, just append the new fork variant on the latest fork. Forks that
// have a distinct execution header will need a new LightClientUdpate variant only
// if you need to test or support lightclient usages
}; };
Ok(light_client_update) Ok(light_client_update)
@@ -247,9 +269,8 @@ impl<E: EthSpec> LightClientUpdate<E> {
Self::Altair(LightClientUpdateAltair::from_ssz_bytes(bytes)?) Self::Altair(LightClientUpdateAltair::from_ssz_bytes(bytes)?)
} }
ForkName::Capella => Self::Capella(LightClientUpdateCapella::from_ssz_bytes(bytes)?), ForkName::Capella => Self::Capella(LightClientUpdateCapella::from_ssz_bytes(bytes)?),
ForkName::Deneb | ForkName::Electra => { ForkName::Deneb => Self::Deneb(LightClientUpdateDeneb::from_ssz_bytes(bytes)?),
Self::Deneb(LightClientUpdateDeneb::from_ssz_bytes(bytes)?) ForkName::Electra => Self::Electra(LightClientUpdateElectra::from_ssz_bytes(bytes)?),
}
ForkName::Base => { ForkName::Base => {
return Err(ssz::DecodeError::BytesInvalid(format!( return Err(ssz::DecodeError::BytesInvalid(format!(
"LightClientUpdate decoding for {fork_name} not implemented" "LightClientUpdate decoding for {fork_name} not implemented"

View File

@@ -34,7 +34,9 @@ use tree_hash_derive::TreeHash;
), ),
serde(bound = "E: EthSpec"), serde(bound = "E: EthSpec"),
arbitrary(bound = "E: EthSpec"), arbitrary(bound = "E: EthSpec"),
) ),
map_into(Attestation),
map_ref_into(AggregateAndProofRef)
)] )]
#[derive( #[derive(
arbitrary::Arbitrary, Debug, Clone, PartialEq, Serialize, Deserialize, Encode, TreeHash, arbitrary::Arbitrary, Debug, Clone, PartialEq, Serialize, Deserialize, Encode, TreeHash,
@@ -102,19 +104,17 @@ impl<E: EthSpec> SignedAggregateAndProof<E> {
} }
} }
pub fn message(&self) -> AggregateAndProofRef<E> { pub fn message<'a>(&'a self) -> AggregateAndProofRef<'a, E> {
match self { map_signed_aggregate_and_proof_ref_into_aggregate_and_proof_ref!(
SignedAggregateAndProof::Base(message) => AggregateAndProofRef::Base(&message.message), &'a _,
SignedAggregateAndProof::Electra(message) => { self.to_ref(),
AggregateAndProofRef::Electra(&message.message) |inner, cons| { cons(&inner.message) }
} )
}
} }
pub fn into_attestation(self) -> Attestation<E> { pub fn into_attestation(self) -> Attestation<E> {
match self { map_signed_aggregate_and_proof_into_attestation!(self, |inner, cons| {
Self::Base(att) => Attestation::Base(att.message.aggregate), cons(inner.message.aggregate)
Self::Electra(att) => Attestation::Electra(att.message.aggregate), })
}
} }
} }

View File

@@ -63,13 +63,6 @@ impl<E: EthSpec> SyncCommitteeContribution<E> {
}) })
} }
/// Are the aggregation bitfields of these sync contribution disjoint?
pub fn signers_disjoint_from(&self, other: &Self) -> bool {
self.aggregation_bits
.intersection(&other.aggregation_bits)
.is_zero()
}
/// Aggregate another `SyncCommitteeContribution` into this one. /// Aggregate another `SyncCommitteeContribution` into this one.
/// ///
/// The aggregation bitfields must be disjoint, and the data must be the same. /// The aggregation bitfields must be disjoint, and the data must be the same.
@@ -77,7 +70,6 @@ impl<E: EthSpec> SyncCommitteeContribution<E> {
debug_assert_eq!(self.slot, other.slot); debug_assert_eq!(self.slot, other.slot);
debug_assert_eq!(self.beacon_block_root, other.beacon_block_root); debug_assert_eq!(self.beacon_block_root, other.beacon_block_root);
debug_assert_eq!(self.subcommittee_index, other.subcommittee_index); debug_assert_eq!(self.subcommittee_index, other.subcommittee_index);
debug_assert!(self.signers_disjoint_from(other));
self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits); self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits);
self.signature.add_assign_aggregate(&other.signature); self.signature.add_assign_aggregate(&other.signature);

View File

@@ -29,6 +29,7 @@ tree_hash = { workspace = true }
tree_hash_derive = { workspace = true } tree_hash_derive = { workspace = true }
types = { workspace = true } types = { workspace = true }
strum = { workspace = true } strum = { workspace = true }
ssz_types = { workspace = true }
# MDBX is pinned at the last version with Windows and macOS support. # MDBX is pinned at the last version with Windows and macOS support.
mdbx = { package = "libmdbx", git = "https://github.com/sigp/libmdbx-rs", tag = "v0.1.4", optional = true } mdbx = { package = "libmdbx", git = "https://github.com/sigp/libmdbx-rs", tag = "v0.1.4", optional = true }

View File

@@ -11,7 +11,7 @@ pub const DEFAULT_VALIDATOR_CHUNK_SIZE: usize = 256;
pub const DEFAULT_HISTORY_LENGTH: usize = 4096; pub const DEFAULT_HISTORY_LENGTH: usize = 4096;
pub const DEFAULT_UPDATE_PERIOD: u64 = 12; pub const DEFAULT_UPDATE_PERIOD: u64 = 12;
pub const DEFAULT_SLOT_OFFSET: f64 = 10.5; pub const DEFAULT_SLOT_OFFSET: f64 = 10.5;
pub const DEFAULT_MAX_DB_SIZE: usize = 256 * 1024; // 256 GiB pub const DEFAULT_MAX_DB_SIZE: usize = 512 * 1024; // 512 GiB
pub const DEFAULT_ATTESTATION_ROOT_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(100_000); pub const DEFAULT_ATTESTATION_ROOT_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(100_000);
pub const DEFAULT_BROADCAST: bool = false; pub const DEFAULT_BROADCAST: bool = false;

View File

@@ -13,17 +13,19 @@ use parking_lot::Mutex;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use slog::{info, Logger}; use slog::{info, Logger};
use ssz::{Decode, Encode}; use ssz::{Decode, Encode};
use ssz_derive::{Decode, Encode};
use std::borrow::{Borrow, Cow}; use std::borrow::{Borrow, Cow};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::Arc; use std::sync::Arc;
use tree_hash::TreeHash; use tree_hash::TreeHash;
use types::{ use types::{
Epoch, EthSpec, Hash256, IndexedAttestation, IndexedAttestationOnDisk, AggregateSignature, AttestationData, ChainSpec, Epoch, EthSpec, Hash256, IndexedAttestation,
IndexedAttestationRefOnDisk, ProposerSlashing, SignedBeaconBlockHeader, Slot, IndexedAttestationBase, IndexedAttestationElectra, ProposerSlashing, SignedBeaconBlockHeader,
Slot, VariableList,
}; };
/// Current database schema version, to check compatibility of on-disk DB with software. /// Current database schema version, to check compatibility of on-disk DB with software.
pub const CURRENT_SCHEMA_VERSION: u64 = 4; pub const CURRENT_SCHEMA_VERSION: u64 = 3;
/// Metadata about the slashing database itself. /// Metadata about the slashing database itself.
const METADATA_DB: &str = "metadata"; const METADATA_DB: &str = "metadata";
@@ -70,6 +72,7 @@ pub struct SlasherDB<E: EthSpec> {
/// LRU cache mapping indexed attestation IDs to their attestation data roots. /// LRU cache mapping indexed attestation IDs to their attestation data roots.
attestation_root_cache: Mutex<LruCache<IndexedAttestationId, Hash256>>, attestation_root_cache: Mutex<LruCache<IndexedAttestationId, Hash256>>,
pub(crate) config: Arc<Config>, pub(crate) config: Arc<Config>,
pub(crate) spec: Arc<ChainSpec>,
_phantom: PhantomData<E>, _phantom: PhantomData<E>,
} }
@@ -236,6 +239,43 @@ impl AsRef<[u8]> for IndexedAttestationId {
} }
} }
/// Indexed attestation that abstracts over Phase0 and Electra variants by using a plain `Vec` for
/// the attesting indices.
///
/// This allows us to avoid rewriting the entire indexed attestation database at Electra, which
/// saves a lot of execution time. The bytes that it encodes to are the same as the bytes that a
/// regular IndexedAttestation encodes to, because SSZ doesn't care about the length-bound.
#[derive(Debug, PartialEq, Decode, Encode)]
pub struct IndexedAttestationOnDisk {
attesting_indices: Vec<u64>,
data: AttestationData,
signature: AggregateSignature,
}
impl IndexedAttestationOnDisk {
fn into_indexed_attestation<E: EthSpec>(
self,
spec: &ChainSpec,
) -> Result<IndexedAttestation<E>, Error> {
let fork_at_target_epoch = spec.fork_name_at_epoch(self.data.target.epoch);
if fork_at_target_epoch.electra_enabled() {
let attesting_indices = VariableList::new(self.attesting_indices)?;
Ok(IndexedAttestation::Electra(IndexedAttestationElectra {
attesting_indices,
data: self.data,
signature: self.signature,
}))
} else {
let attesting_indices = VariableList::new(self.attesting_indices)?;
Ok(IndexedAttestation::Base(IndexedAttestationBase {
attesting_indices,
data: self.data,
signature: self.signature,
}))
}
}
}
/// Bincode deserialization specialised to `Cow<[u8]>`. /// Bincode deserialization specialised to `Cow<[u8]>`.
fn bincode_deserialize<T: DeserializeOwned>(bytes: Cow<[u8]>) -> Result<T, Error> { fn bincode_deserialize<T: DeserializeOwned>(bytes: Cow<[u8]>) -> Result<T, Error> {
Ok(bincode::deserialize(bytes.borrow())?) Ok(bincode::deserialize(bytes.borrow())?)
@@ -246,7 +286,7 @@ fn ssz_decode<T: Decode>(bytes: Cow<[u8]>) -> Result<T, Error> {
} }
impl<E: EthSpec> SlasherDB<E> { impl<E: EthSpec> SlasherDB<E> {
pub fn open(config: Arc<Config>, log: Logger) -> Result<Self, Error> { pub fn open(config: Arc<Config>, spec: Arc<ChainSpec>, log: Logger) -> Result<Self, Error> {
info!(log, "Opening slasher database"; "backend" => %config.backend); info!(log, "Opening slasher database"; "backend" => %config.backend);
std::fs::create_dir_all(&config.database_path)?; std::fs::create_dir_all(&config.database_path)?;
@@ -269,6 +309,7 @@ impl<E: EthSpec> SlasherDB<E> {
databases, databases,
attestation_root_cache, attestation_root_cache,
config, config,
spec,
_phantom: PhantomData, _phantom: PhantomData,
}; };
@@ -458,9 +499,8 @@ impl<E: EthSpec> SlasherDB<E> {
}; };
let attestation_key = IndexedAttestationId::new(indexed_att_id); let attestation_key = IndexedAttestationId::new(indexed_att_id);
let indexed_attestation_on_disk: IndexedAttestationRefOnDisk<E> = // IndexedAttestationOnDisk and IndexedAttestation have compatible encodings.
indexed_attestation.into(); let data = indexed_attestation.as_ssz_bytes();
let data = indexed_attestation_on_disk.as_ssz_bytes();
cursor.put(attestation_key.as_ref(), &data)?; cursor.put(attestation_key.as_ref(), &data)?;
drop(cursor); drop(cursor);
@@ -484,8 +524,8 @@ impl<E: EthSpec> SlasherDB<E> {
.ok_or(Error::MissingIndexedAttestation { .ok_or(Error::MissingIndexedAttestation {
id: indexed_attestation_id.as_u64(), id: indexed_attestation_id.as_u64(),
})?; })?;
let indexed_attestation: IndexedAttestationOnDisk<E> = ssz_decode(bytes)?; let indexed_attestation_on_disk: IndexedAttestationOnDisk = ssz_decode(bytes)?;
Ok(indexed_attestation.into()) indexed_attestation_on_disk.into_indexed_attestation(&self.spec)
} }
fn get_attestation_data_root( fn get_attestation_data_root(
@@ -775,3 +815,93 @@ impl<E: EthSpec> SlasherDB<E> {
Ok(()) Ok(())
} }
} }
#[cfg(test)]
mod test {
use super::*;
use types::{Checkpoint, ForkName, MainnetEthSpec, Unsigned};
type E = MainnetEthSpec;
fn indexed_attestation_on_disk_roundtrip_test(
spec: &ChainSpec,
make_attestation: fn(
Vec<u64>,
AttestationData,
AggregateSignature,
) -> IndexedAttestation<E>,
committee_len: u64,
) {
let attestation_data = AttestationData {
slot: Slot::new(1000),
index: 0,
beacon_block_root: Hash256::repeat_byte(0xaa),
source: Checkpoint {
epoch: Epoch::new(0),
root: Hash256::repeat_byte(0xbb),
},
target: Checkpoint {
epoch: Epoch::new(31),
root: Hash256::repeat_byte(0xcc),
},
};
let attesting_indices = (0..committee_len).collect::<Vec<_>>();
let signature = AggregateSignature::infinity();
let fork_attestation = make_attestation(
attesting_indices.clone(),
attestation_data.clone(),
signature.clone(),
);
let on_disk = IndexedAttestationOnDisk {
attesting_indices,
data: attestation_data,
signature,
};
let encoded = on_disk.as_ssz_bytes();
assert_eq!(encoded, fork_attestation.as_ssz_bytes());
let decoded_on_disk = IndexedAttestationOnDisk::from_ssz_bytes(&encoded).unwrap();
assert_eq!(decoded_on_disk, on_disk);
let decoded = on_disk.into_indexed_attestation(spec).unwrap();
assert_eq!(decoded, fork_attestation);
}
/// Check that `IndexedAttestationOnDisk` and `IndexedAttestation` have compatible encodings.
#[test]
fn indexed_attestation_on_disk_roundtrip_base() {
let spec = ForkName::Base.make_genesis_spec(E::default_spec());
let make_attestation = |attesting_indices, data, signature| {
IndexedAttestation::<E>::Base(IndexedAttestationBase {
attesting_indices: VariableList::new(attesting_indices).unwrap(),
data,
signature,
})
};
indexed_attestation_on_disk_roundtrip_test(
&spec,
make_attestation,
<E as EthSpec>::MaxValidatorsPerCommittee::to_u64(),
)
}
#[test]
fn indexed_attestation_on_disk_roundtrip_electra() {
let spec = ForkName::Electra.make_genesis_spec(E::default_spec());
let make_attestation = |attesting_indices, data, signature| {
IndexedAttestation::<E>::Electra(IndexedAttestationElectra {
attesting_indices: VariableList::new(attesting_indices).unwrap(),
data,
signature,
})
};
indexed_attestation_on_disk_roundtrip_test(
&spec,
make_attestation,
<E as EthSpec>::MaxValidatorsPerSlot::to_u64(),
)
}
}

View File

@@ -13,6 +13,7 @@ pub enum Error {
DatabaseIOError(io::Error), DatabaseIOError(io::Error),
DatabasePermissionsError(filesystem::Error), DatabasePermissionsError(filesystem::Error),
SszDecodeError(ssz::DecodeError), SszDecodeError(ssz::DecodeError),
SszTypesError(ssz_types::Error),
BincodeError(bincode::Error), BincodeError(bincode::Error),
ArithError(safe_arith::ArithError), ArithError(safe_arith::ArithError),
ChunkIndexOutOfBounds(usize), ChunkIndexOutOfBounds(usize),
@@ -100,6 +101,12 @@ impl From<ssz::DecodeError> for Error {
} }
} }
impl From<ssz_types::Error> for Error {
fn from(e: ssz_types::Error) -> Self {
Error::SszTypesError(e)
}
}
impl From<bincode::Error> for Error { impl From<bincode::Error> for Error {
fn from(e: bincode::Error) -> Self { fn from(e: bincode::Error) -> Self {
Error::BincodeError(e) Error::BincodeError(e)

View File

@@ -17,10 +17,6 @@ impl<E: EthSpec> SlasherDB<E> {
software_schema_version: CURRENT_SCHEMA_VERSION, software_schema_version: CURRENT_SCHEMA_VERSION,
}), }),
(x, y) if x == y => Ok(self), (x, y) if x == y => Ok(self),
(3, 4) => {
// TODO(electra): db migration due to `IndexedAttestationOnDisk`
Ok(self)
}
(_, _) => Err(Error::IncompatibleSchemaVersion { (_, _) => Err(Error::IncompatibleSchemaVersion {
database_schema_version: schema_version, database_schema_version: schema_version,
software_schema_version: CURRENT_SCHEMA_VERSION, software_schema_version: CURRENT_SCHEMA_VERSION,

View File

@@ -13,7 +13,8 @@ use slog::{debug, error, info, Logger};
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc; use std::sync::Arc;
use types::{ use types::{
AttesterSlashing, Epoch, EthSpec, IndexedAttestation, ProposerSlashing, SignedBeaconBlockHeader, AttesterSlashing, ChainSpec, Epoch, EthSpec, IndexedAttestation, ProposerSlashing,
SignedBeaconBlockHeader,
}; };
#[derive(Debug)] #[derive(Debug)]
@@ -28,10 +29,10 @@ pub struct Slasher<E: EthSpec> {
} }
impl<E: EthSpec> Slasher<E> { impl<E: EthSpec> Slasher<E> {
pub fn open(config: Config, log: Logger) -> Result<Self, Error> { pub fn open(config: Config, spec: Arc<ChainSpec>, log: Logger) -> Result<Self, Error> {
config.validate()?; config.validate()?;
let config = Arc::new(config); let config = Arc::new(config);
let db = SlasherDB::open(config.clone(), log.clone())?; let db = SlasherDB::open(config.clone(), spec, log.clone())?;
let attester_slashings = Mutex::new(HashSet::new()); let attester_slashings = Mutex::new(HashSet::new());
let proposer_slashings = Mutex::new(HashSet::new()); let proposer_slashings = Mutex::new(HashSet::new());
let attestation_queue = AttestationQueue::default(); let attestation_queue = AttestationQueue::default();

View File

@@ -1,9 +1,10 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc;
use types::{ use types::{
indexed_attestation::{IndexedAttestationBase, IndexedAttestationElectra}, indexed_attestation::{IndexedAttestationBase, IndexedAttestationElectra},
AggregateSignature, AttestationData, AttesterSlashing, AttesterSlashingBase, AggregateSignature, AttestationData, AttesterSlashing, AttesterSlashingBase,
AttesterSlashingElectra, BeaconBlockHeader, Checkpoint, Epoch, Hash256, IndexedAttestation, AttesterSlashingElectra, BeaconBlockHeader, ChainSpec, Checkpoint, Epoch, EthSpec, Hash256,
MainnetEthSpec, Signature, SignedBeaconBlockHeader, Slot, IndexedAttestation, MainnetEthSpec, Signature, SignedBeaconBlockHeader, Slot,
}; };
pub type E = MainnetEthSpec; pub type E = MainnetEthSpec;
@@ -145,3 +146,7 @@ pub fn block(slot: u64, proposer_index: u64, block_root: u64) -> SignedBeaconBlo
signature: Signature::empty(), signature: Signature::empty(),
} }
} }
pub fn chain_spec() -> Arc<ChainSpec> {
Arc::new(E::default_spec())
}

View File

@@ -6,7 +6,8 @@ use rayon::prelude::*;
use slasher::{ use slasher::{
config::DEFAULT_CHUNK_SIZE, config::DEFAULT_CHUNK_SIZE,
test_utils::{ test_utils::{
att_slashing, indexed_att, indexed_att_electra, slashed_validators_from_slashings, E, att_slashing, chain_spec, indexed_att, indexed_att_electra,
slashed_validators_from_slashings, E,
}, },
Config, Slasher, Config, Slasher,
}; };
@@ -270,7 +271,8 @@ fn slasher_test(
) { ) {
let tempdir = tempdir().unwrap(); let tempdir = tempdir().unwrap();
let config = Config::new(tempdir.path().into()); let config = Config::new(tempdir.path().into());
let slasher = Slasher::open(config, test_logger()).unwrap(); let spec = chain_spec();
let slasher = Slasher::open(config, spec, test_logger()).unwrap();
let current_epoch = Epoch::new(current_epoch); let current_epoch = Epoch::new(current_epoch);
for (i, attestation) in attestations.iter().enumerate() { for (i, attestation) in attestations.iter().enumerate() {
@@ -299,7 +301,8 @@ fn parallel_slasher_test(
) { ) {
let tempdir = tempdir().unwrap(); let tempdir = tempdir().unwrap();
let config = Config::new(tempdir.path().into()); let config = Config::new(tempdir.path().into());
let slasher = Slasher::open(config, test_logger()).unwrap(); let spec = chain_spec();
let slasher = Slasher::open(config, spec, test_logger()).unwrap();
let current_epoch = Epoch::new(current_epoch); let current_epoch = Epoch::new(current_epoch);
attestations attestations

View File

@@ -2,7 +2,7 @@
use logging::test_logger; use logging::test_logger;
use slasher::{ use slasher::{
test_utils::{block as test_block, E}, test_utils::{block as test_block, chain_spec, E},
Config, Slasher, Config, Slasher,
}; };
use tempfile::tempdir; use tempfile::tempdir;
@@ -12,7 +12,8 @@ use types::{Epoch, EthSpec};
fn empty_pruning() { fn empty_pruning() {
let tempdir = tempdir().unwrap(); let tempdir = tempdir().unwrap();
let config = Config::new(tempdir.path().into()); let config = Config::new(tempdir.path().into());
let slasher = Slasher::<E>::open(config, test_logger()).unwrap(); let spec = chain_spec();
let slasher = Slasher::<E>::open(config, spec, test_logger()).unwrap();
slasher.prune_database(Epoch::new(0)).unwrap(); slasher.prune_database(Epoch::new(0)).unwrap();
} }
@@ -24,8 +25,9 @@ fn block_pruning() {
let mut config = Config::new(tempdir.path().into()); let mut config = Config::new(tempdir.path().into());
config.chunk_size = 2; config.chunk_size = 2;
config.history_length = 2; config.history_length = 2;
let spec = chain_spec();
let slasher = Slasher::<E>::open(config.clone(), test_logger()).unwrap(); let slasher = Slasher::<E>::open(config.clone(), spec, test_logger()).unwrap();
let current_epoch = Epoch::from(2 * config.history_length); let current_epoch = Epoch::from(2 * config.history_length);
// Pruning the empty database should be safe. // Pruning the empty database should be safe.

View File

@@ -4,7 +4,7 @@ use logging::test_logger;
use rand::prelude::*; use rand::prelude::*;
use slasher::{ use slasher::{
test_utils::{ test_utils::{
block, indexed_att, slashed_validators_from_attestations, block, chain_spec, indexed_att, slashed_validators_from_attestations,
slashed_validators_from_slashings, E, slashed_validators_from_slashings, E,
}, },
Config, Slasher, Config, Slasher,
@@ -49,7 +49,9 @@ fn random_test(seed: u64, test_config: TestConfig) {
config.chunk_size = 1 << chunk_size_exponent; config.chunk_size = 1 << chunk_size_exponent;
config.history_length = 1 << rng.gen_range(chunk_size_exponent..chunk_size_exponent + 3); config.history_length = 1 << rng.gen_range(chunk_size_exponent..chunk_size_exponent + 3);
let slasher = Slasher::<E>::open(config.clone(), test_logger()).unwrap(); let spec = chain_spec();
let slasher = Slasher::<E>::open(config.clone(), spec, test_logger()).unwrap();
let validators = (0..num_validators as u64).collect::<Vec<u64>>(); let validators = (0..num_validators as u64).collect::<Vec<u64>>();

View File

@@ -1,7 +1,10 @@
#![cfg(any(feature = "mdbx", feature = "lmdb"))] #![cfg(any(feature = "mdbx", feature = "lmdb"))]
use logging::test_logger; use logging::test_logger;
use slasher::{test_utils::indexed_att, Config, Slasher}; use slasher::{
test_utils::{chain_spec, indexed_att},
Config, Slasher,
};
use tempfile::tempdir; use tempfile::tempdir;
use types::Epoch; use types::Epoch;
@@ -9,11 +12,12 @@ use types::Epoch;
fn attestation_pruning_empty_wrap_around() { fn attestation_pruning_empty_wrap_around() {
let tempdir = tempdir().unwrap(); let tempdir = tempdir().unwrap();
let mut config = Config::new(tempdir.path().into()); let mut config = Config::new(tempdir.path().into());
let spec = chain_spec();
config.validator_chunk_size = 1; config.validator_chunk_size = 1;
config.chunk_size = 16; config.chunk_size = 16;
config.history_length = 16; config.history_length = 16;
let slasher = Slasher::open(config.clone(), test_logger()).unwrap(); let slasher = Slasher::open(config.clone(), spec, test_logger()).unwrap();
let v = vec![0]; let v = vec![0];
let history_length = config.history_length as u64; let history_length = config.history_length as u64;

View File

@@ -43,8 +43,7 @@ excluded_paths = [
"bls12-381-tests/hash_to_G2", "bls12-381-tests/hash_to_G2",
"tests/.*/eip6110", "tests/.*/eip6110",
"tests/.*/whisk", "tests/.*/whisk",
"tests/.*/eip7594", "tests/.*/eip7594"
"tests/.*/electra/ssz_static/LightClient*"
] ]

View File

@@ -41,6 +41,8 @@ type_name_generic!(AggregateAndProof);
type_name_generic!(AggregateAndProofBase, "AggregateAndProof"); type_name_generic!(AggregateAndProofBase, "AggregateAndProof");
type_name_generic!(AggregateAndProofElectra, "AggregateAndProof"); type_name_generic!(AggregateAndProofElectra, "AggregateAndProof");
type_name_generic!(Attestation); type_name_generic!(Attestation);
type_name_generic!(AttestationBase, "Attestation");
type_name_generic!(AttestationElectra, "Attestation");
type_name!(AttestationData); type_name!(AttestationData);
type_name_generic!(AttesterSlashing); type_name_generic!(AttesterSlashing);
type_name_generic!(AttesterSlashingBase, "AttesterSlashing"); type_name_generic!(AttesterSlashingBase, "AttesterSlashing");