From 388a7cd05b535258fea2134ade12665758ff7efa Mon Sep 17 00:00:00 2001 From: geemo Date: Tue, 7 Feb 2023 14:58:12 -0600 Subject: [PATCH 01/72] rebase and add comment --- beacon_node/http_api/src/lib.rs | 93 +++++++++++++++++++++++++++++ beacon_node/http_api/tests/tests.rs | 51 +++++++++++++++- common/eth2/src/lib.rs | 34 +++++++++++ lighthouse/src/main.rs | 2 +- 4 files changed, 178 insertions(+), 2 deletions(-) diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 973be2d49b4..78cf5c02dd8 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -1726,6 +1726,97 @@ pub fn serve( * beacon/rewards */ + let beacon_light_client_path = eth_v1 + .and(warp::path("beacon")) + .and(warp::path("light_client")) + .and(chain_filter.clone()); + + // GET beacon/light_client/optimistic_update + let get_beacon_light_client_optimistic_update = beacon_light_client_path + .clone() + .and(warp::path("optimistic_update")) + .and(warp::path::end()) + .and(warp::header::optional::("accept")) + .and_then( + |chain: Arc>, accept_header: Option| { + blocking_task(move || { + let update = chain + .latest_seen_optimistic_update + .lock() + .clone() + .ok_or_else(|| { + warp_utils::reject::custom_not_found(format!( + "No LightClientOptimisticUpdate is available" + )) + })?; + + let fork_name = chain + .spec + .fork_name_at_slot::(update.signature_slot); + match accept_header { + Some(api_types::Accept::Ssz) => Response::builder() + .status(200) + .header("Content-Type", "application/octet-stream") + .body(update.as_ssz_bytes().into()) + .map_err(|e| { + warp_utils::reject::custom_server_error(format!( + "failed to create response: {}", + e + )) + }), + _ => Ok(warp::reply::json(&api_types::GenericResponse::from(update)) + .into_response()), + } + .map(|resp| add_consensus_version_header(resp, fork_name)) + }) + }, + ); + + // GET beacon/light_client/finality_update + let get_beacon_light_client_finality_update = beacon_light_client_path + .clone() + .and(warp::path("finality_update")) + .and(warp::path::end()) + .and(warp::header::optional::("accept")) + .and_then( + |chain: Arc>, accept_header: Option| { + blocking_task(move || { + let update = chain + .latest_seen_finality_update + .lock() + .clone() + .ok_or_else(|| { + warp_utils::reject::custom_not_found(format!( + "No LightClientFinalityUpdate is available" + )) + })?; + + let fork_name = chain + .spec + .fork_name_at_slot::(update.signature_slot); + match accept_header { + Some(api_types::Accept::Ssz) => Response::builder() + .status(200) + .header("Content-Type", "application/octet-stream") + .body(update.as_ssz_bytes().into()) + .map_err(|e| { + warp_utils::reject::custom_server_error(format!( + "failed to create response: {}", + e + )) + }), + _ => Ok(warp::reply::json(&api_types::GenericResponse::from(update)) + .into_response()), + } + .map(|resp| add_consensus_version_header(resp, fork_name)) + }) + }, + ); + + /* + * beacon/rewards + */ + let beacon_rewards_path = eth_v1 .and(warp::path("beacon")) .and(warp::path("rewards")) @@ -3456,6 +3547,8 @@ pub fn serve( .or(get_beacon_pool_voluntary_exits.boxed()) .or(get_beacon_deposit_snapshot.boxed()) .or(get_beacon_rewards_blocks.boxed()) + .or(get_beacon_light_client_optimistic_update.boxed()) + .or(get_beacon_light_client_finality_update.boxed()) .or(get_config_fork_schedule.boxed()) .or(get_config_spec.boxed()) .or(get_config_deposit_contract.boxed()) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 2e795e522d5..ab2b347fced 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -31,7 +31,7 @@ use tree_hash::TreeHash; use types::application_domain::ApplicationDomain; use types::{ AggregateSignature, BitList, Domain, EthSpec, ExecutionBlockHash, Hash256, Keypair, - MainnetEthSpec, RelativeEpoch, SelectionProof, SignedRoot, Slot, + LightClientOptimisticUpdate, MainnetEthSpec, RelativeEpoch, SelectionProof, SignedRoot, Slot, }; type E = MainnetEthSpec; @@ -1202,6 +1202,39 @@ impl ApiTester { self } + pub async fn test_get_beacon_light_client_optimistic_update(self) -> Self { + // get_beacon_light_client_optimistic_update returns Ok(None) on 404 NOT FOUND + let result = match self + .client + .get_beacon_light_client_optimistic_update::() + .await + { + Ok(result) => result, + Err(_) => panic!("query did not fail correctly"), + }; + + let expected = self.chain.latest_seen_optimistic_update.lock().clone(); + assert_eq!(result, expected); + + self + } + + pub async fn test_get_beacon_light_client_finality_update(self) -> Self { + let result = match self + .client + .get_beacon_light_client_finality_update::() + .await + { + Ok(result) => result, + Err(_) => panic!("query did not fail correctly"), + }; + + let expected = self.chain.latest_seen_finality_update.lock().clone(); + assert_eq!(result, expected); + + self + } + pub async fn test_get_beacon_pool_attestations(self) -> Self { let result = self .client @@ -3947,6 +3980,22 @@ async fn node_get() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_light_client_optimistic_update() { + ApiTester::new() + .await + .test_get_beacon_light_client_optimistic_update() + .await; +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_light_client_finality_update() { + ApiTester::new() + .await + .test_get_beacon_light_client_finality_update() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_validator_duties_early() { ApiTester::new() diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 653c6c0bcc7..77f7088e722 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -562,6 +562,40 @@ impl BeaconNodeHttpClient { self.get_opt(path).await } + /// `GET beacon/light_client/optimimistic_update` + /// + /// Returns `Ok(None)` on a 404 error. + pub async fn get_beacon_light_client_optimistic_update( + &self, + ) -> Result>, Error> { + let mut path = self.eth_path(V1)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("beacon") + .push("light_client") + .push("optimistic_update"); + + self.get_opt(path).await + } + + /// `GET beacon/light_client/finality_update` + /// + /// Returns `Ok(None)` on a 404 error. + pub async fn get_beacon_light_client_finality_update( + &self, + ) -> Result>, Error> { + let mut path = self.eth_path(V1)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("beacon") + .push("light_client") + .push("finality_update"); + + self.get_opt(path).await + } + /// `GET beacon/headers?slot,parent_root` /// /// Returns `Ok(None)` on a 404 error. diff --git a/lighthouse/src/main.rs b/lighthouse/src/main.rs index babe2f8dca7..d9ad22decbd 100644 --- a/lighthouse/src/main.rs +++ b/lighthouse/src/main.rs @@ -1,4 +1,4 @@ -#![recursion_limit = "256"] +#![recursion_limit = "512"] mod metrics; From b47b4f25c258c858473d12b0be2bf7b2ce9a8bd6 Mon Sep 17 00:00:00 2001 From: geemo Date: Wed, 8 Feb 2023 12:36:31 -0600 Subject: [PATCH 02/72] conditional test --- beacon_node/client/src/builder.rs | 1 + beacon_node/http_api/src/lib.rs | 20 ++++++++++++++++++-- beacon_node/http_api/tests/common.rs | 1 + beacon_node/http_api/tests/main.rs | 2 +- beacon_node/http_api/tests/tests.rs | 2 +- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index 3b016ebda9c..a1eecbc9814 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -147,6 +147,7 @@ where } else { None }; + self.http_api_config.enable_light_client_server = config.network.enable_light_client_server; let execution_layer = if let Some(config) = config.execution_layer { let context = runtime_context.service_context("exec".into()); diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 78cf5c02dd8..b4aaccbb67b 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -118,6 +118,7 @@ pub struct Config { pub allow_sync_stalled: bool, pub spec_fork_name: Option, pub data_dir: PathBuf, + pub enable_light_client_server: bool, } impl Default for Config { @@ -131,6 +132,7 @@ impl Default for Config { allow_sync_stalled: false, spec_fork_name: None, data_dir: PathBuf::from(DEFAULT_ROOT_DIR), + enable_light_client_server: false, } } } @@ -251,6 +253,18 @@ pub fn prometheus_metrics() -> warp::filters::log::Log impl Filter + Clone { + warp::any() + .and_then(move || async move { + if is_enabled { + Ok(()) + } else { + Err(warp::reject::not_found()) + } + }) + .untuple_one() +} + /// Creates a server that will serve requests using information from `ctx`. /// /// The server will shut down gracefully when the `shutdown` future resolves. @@ -3547,8 +3561,6 @@ pub fn serve( .or(get_beacon_pool_voluntary_exits.boxed()) .or(get_beacon_deposit_snapshot.boxed()) .or(get_beacon_rewards_blocks.boxed()) - .or(get_beacon_light_client_optimistic_update.boxed()) - .or(get_beacon_light_client_finality_update.boxed()) .or(get_config_fork_schedule.boxed()) .or(get_config_spec.boxed()) .or(get_config_deposit_contract.boxed()) @@ -3591,6 +3603,10 @@ pub fn serve( .recover(warp_utils::reject::handle_rejection), ) .boxed() + .or(enable(true) + .and(get_beacon_light_client_optimistic_update.boxed()) + ) + .boxed() .or(warp::post().and( post_beacon_blocks .boxed() diff --git a/beacon_node/http_api/tests/common.rs b/beacon_node/http_api/tests/common.rs index 7c228d9803f..ee85db8fc44 100644 --- a/beacon_node/http_api/tests/common.rs +++ b/beacon_node/http_api/tests/common.rs @@ -176,6 +176,7 @@ pub async fn create_api_server_on_port( allow_sync_stalled: false, data_dir: std::path::PathBuf::from(DEFAULT_ROOT_DIR), spec_fork_name: None, + enable_light_client_server: true, }, chain: Some(chain.clone()), network_senders: Some(network_senders), diff --git a/beacon_node/http_api/tests/main.rs b/beacon_node/http_api/tests/main.rs index ca6a27530a6..76a851dc4d2 100644 --- a/beacon_node/http_api/tests/main.rs +++ b/beacon_node/http_api/tests/main.rs @@ -1,5 +1,5 @@ #![cfg(not(debug_assertions))] // Tests are too slow in debug. -#![recursion_limit = "256"] +#![recursion_limit = "512"] pub mod common; pub mod fork_tests; diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index ab2b347fced..4058160771f 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -1210,7 +1210,7 @@ impl ApiTester { .await { Ok(result) => result, - Err(_) => panic!("query did not fail correctly"), + Err(e) => panic!("query did not fail correctly"), }; let expected = self.chain.latest_seen_optimistic_update.lock().clone(); From df78885f93e05a3986341c8e778f133652c26c44 Mon Sep 17 00:00:00 2001 From: geemo Date: Wed, 8 Feb 2023 13:18:29 -0600 Subject: [PATCH 03/72] test --- beacon_node/http_api/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index b4aaccbb67b..409b068e0ac 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -3604,7 +3604,7 @@ pub fn serve( ) .boxed() .or(enable(true) - .and(get_beacon_light_client_optimistic_update.boxed()) + .and(get_beacon_light_client_optimistic_update) ) .boxed() .or(warp::post().and( From e124c995220d6181802d3cdf1a36799d4751ab0e Mon Sep 17 00:00:00 2001 From: geemo Date: Wed, 8 Feb 2023 13:47:46 -0600 Subject: [PATCH 04/72] optimistic chould be working now --- beacon_node/http_api/src/lib.rs | 7 +++---- beacon_node/http_api/tests/tests.rs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 409b068e0ac..51c459b3545 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -3597,16 +3597,15 @@ pub fn serve( .or(get_lighthouse_database_info.boxed()) .or(get_lighthouse_block_rewards.boxed()) .or(get_lighthouse_attestation_performance.boxed()) + .or(enable(ctx.config.enable_light_client_server) + .and(get_beacon_light_client_optimistic_update.boxed()) + ) .or(get_lighthouse_block_packing_efficiency.boxed()) .or(get_lighthouse_merge_readiness.boxed()) .or(get_events.boxed()) .recover(warp_utils::reject::handle_rejection), ) .boxed() - .or(enable(true) - .and(get_beacon_light_client_optimistic_update) - ) - .boxed() .or(warp::post().and( post_beacon_blocks .boxed() diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 4058160771f..945847b7365 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -31,7 +31,7 @@ use tree_hash::TreeHash; use types::application_domain::ApplicationDomain; use types::{ AggregateSignature, BitList, Domain, EthSpec, ExecutionBlockHash, Hash256, Keypair, - LightClientOptimisticUpdate, MainnetEthSpec, RelativeEpoch, SelectionProof, SignedRoot, Slot, + MainnetEthSpec, RelativeEpoch, SelectionProof, SignedRoot, Slot, }; type E = MainnetEthSpec; From cfdeca0650fcf195132ef5e9f597a6325add198a Mon Sep 17 00:00:00 2001 From: geemo Date: Wed, 8 Feb 2023 14:00:01 -0600 Subject: [PATCH 05/72] finality should be working now --- beacon_node/http_api/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 51c459b3545..e56d63b395b 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -3599,6 +3599,7 @@ pub fn serve( .or(get_lighthouse_attestation_performance.boxed()) .or(enable(ctx.config.enable_light_client_server) .and(get_beacon_light_client_optimistic_update.boxed()) + .or(get_beacon_light_client_finality_update.boxed()) ) .or(get_lighthouse_block_packing_efficiency.boxed()) .or(get_lighthouse_merge_readiness.boxed()) From e15b98f4709484478ab062885f73faafdb0193f4 Mon Sep 17 00:00:00 2001 From: geemo Date: Wed, 8 Feb 2023 14:08:15 -0600 Subject: [PATCH 06/72] try again --- beacon_node/http_api/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index e56d63b395b..b3fb75ec475 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -3599,7 +3599,7 @@ pub fn serve( .or(get_lighthouse_attestation_performance.boxed()) .or(enable(ctx.config.enable_light_client_server) .and(get_beacon_light_client_optimistic_update.boxed()) - .or(get_beacon_light_client_finality_update.boxed()) + .and(get_beacon_light_client_finality_update.boxed()) ) .or(get_lighthouse_block_packing_efficiency.boxed()) .or(get_lighthouse_merge_readiness.boxed()) From 6db8a414b3c4ca834bff9dc1f6670cd6e10472a5 Mon Sep 17 00:00:00 2001 From: geemo Date: Wed, 8 Feb 2023 14:10:09 -0600 Subject: [PATCH 07/72] try again --- beacon_node/http_api/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index b3fb75ec475..d0123c5be03 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -3599,6 +3599,8 @@ pub fn serve( .or(get_lighthouse_attestation_performance.boxed()) .or(enable(ctx.config.enable_light_client_server) .and(get_beacon_light_client_optimistic_update.boxed()) + ) + .or(enable(ctx.config.enable_light_client_server) .and(get_beacon_light_client_finality_update.boxed()) ) .or(get_lighthouse_block_packing_efficiency.boxed()) From 1537947ee582b3560cba61e8866ef78708d1580d Mon Sep 17 00:00:00 2001 From: geemo Date: Wed, 8 Feb 2023 14:30:40 -0600 Subject: [PATCH 08/72] clippy fix --- beacon_node/http_api/src/lib.rs | 18 ++++++++---------- beacon_node/http_api/tests/tests.rs | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index d0123c5be03..27f5c998904 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -1759,9 +1759,9 @@ pub fn serve( .lock() .clone() .ok_or_else(|| { - warp_utils::reject::custom_not_found(format!( - "No LightClientOptimisticUpdate is available" - )) + warp_utils::reject::custom_not_found( + "No LightClientOptimisticUpdate is available".to_string(), + ) })?; let fork_name = chain @@ -1800,9 +1800,9 @@ pub fn serve( .lock() .clone() .ok_or_else(|| { - warp_utils::reject::custom_not_found(format!( - "No LightClientFinalityUpdate is available" - )) + warp_utils::reject::custom_not_found( + "No LightClientFinalityUpdate is available".to_string(), + ) })?; let fork_name = chain @@ -3598,11 +3598,9 @@ pub fn serve( .or(get_lighthouse_block_rewards.boxed()) .or(get_lighthouse_attestation_performance.boxed()) .or(enable(ctx.config.enable_light_client_server) - .and(get_beacon_light_client_optimistic_update.boxed()) - ) + .and(get_beacon_light_client_optimistic_update.boxed())) .or(enable(ctx.config.enable_light_client_server) - .and(get_beacon_light_client_finality_update.boxed()) - ) + .and(get_beacon_light_client_finality_update.boxed())) .or(get_lighthouse_block_packing_efficiency.boxed()) .or(get_lighthouse_merge_readiness.boxed()) .or(get_events.boxed()) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 945847b7365..5960d1bfb9a 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -1210,7 +1210,7 @@ impl ApiTester { .await { Ok(result) => result, - Err(e) => panic!("query did not fail correctly"), + Err(_) => panic!("query did not fail correctly"), }; let expected = self.chain.latest_seen_optimistic_update.lock().clone(); From 5dbde26a64b99fd437f5fbeee6ca80d4f5cb90c6 Mon Sep 17 00:00:00 2001 From: geemo Date: Wed, 8 Feb 2023 19:57:16 -0600 Subject: [PATCH 09/72] add lc bootstrap beacon api --- beacon_node/http_api/src/lib.rs | 71 ++++++++++++++++++- .../src/rpc/codec/ssz_snappy.rs | 2 +- .../lighthouse_network/src/rpc/methods.rs | 2 +- .../src/service/api_types.rs | 2 +- .../beacon_processor/worker/rpc_methods.rs | 2 +- consensus/types/src/lib.rs | 1 + 6 files changed, 75 insertions(+), 5 deletions(-) diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 27f5c998904..091d81b79d3 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -58,7 +58,7 @@ use types::{ ProposerPreparationData, ProposerSlashing, RelativeEpoch, SignedAggregateAndProof, SignedBeaconBlock, SignedBlindedBeaconBlock, SignedContributionAndProof, SignedValidatorRegistrationData, SignedVoluntaryExit, Slot, SyncCommitteeMessage, - SyncContributionData, + SyncContributionData, Hash256, LightClientBootstrap, }; use version::{ add_consensus_version_header, execution_optimistic_fork_versioned_response, @@ -1745,6 +1745,73 @@ pub fn serve( .and(warp::path("light_client")) .and(chain_filter.clone()); + // GET beacon/light_client/bootrap/{block_root} + let get_beacon_light_client_bootstrap = beacon_light_client_path + .clone() + .and(warp::path("bootstrap")) + .and(warp::path::param::().or_else(|_| async { + Err(warp_utils::reject::custom_bad_request( + "Invalid block root value".to_string(), + )) + })) + .and(warp::path::end()) + .and(warp::header::optional::("accept")) + .and_then( + |chain: Arc>, + block_root: Hash256, + accept_header: Option| { + blocking_task(move || { + let state_root = chain + .get_blinded_block(&block_root) + .map_err(|_| { + warp_utils::reject::custom_server_error( + "Error retrieving block".to_string(), + ) + })? + .map(|signed_block| signed_block.state_root()) + .ok_or_else(|| { + warp_utils::reject::custom_not_found( + "Light client bootstrap unavailable".to_string(), + ) + })?; + + let mut state = chain.get_state(&state_root, None).map_err(|_| { + warp_utils::reject::custom_server_error( + "Error retrieving state".to_string(), + ) + })? + .ok_or_else(|| { + warp_utils::reject::custom_not_found("Light client bootstrap unavailable".to_string()) + })?; + let fork_name = state + .fork_name(&chain.spec) + .map_err(inconsistent_fork_rejection)?; + let bootstrap = LightClientBootstrap::from_beacon_state(&mut state) + .map_err(|_| { + warp_utils::reject::custom_server_error( + "Failed to create light client bootstrap".to_string(), + ) + })?; + match accept_header { + Some(api_types::Accept::Ssz) => Response::builder() + .status(200) + .header("Content-Type", "application/octet-stream") + .body(bootstrap.as_ssz_bytes().into()) + .map_err(|e| { + warp_utils::reject::custom_server_error(format!( + "failed to create response: {}", + e + )) + }), + _ => Ok(warp::reply::json(&api_types::GenericResponse::from(bootstrap)) + .into_response()), + } + .map(|resp| add_consensus_version_header(resp, fork_name)) + }) + } + ); + + // GET beacon/light_client/optimistic_update let get_beacon_light_client_optimistic_update = beacon_light_client_path .clone() @@ -3601,6 +3668,8 @@ pub fn serve( .and(get_beacon_light_client_optimistic_update.boxed())) .or(enable(ctx.config.enable_light_client_server) .and(get_beacon_light_client_finality_update.boxed())) + .or(enable(ctx.config.enable_light_client_server) + .and(get_beacon_light_client_bootstrap.boxed())) .or(get_lighthouse_block_packing_efficiency.boxed()) .or(get_lighthouse_merge_readiness.boxed()) .or(get_events.boxed()) diff --git a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs index eccbf0dd623..96b0cd26eb6 100644 --- a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs +++ b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs @@ -16,7 +16,7 @@ use std::marker::PhantomData; use std::sync::Arc; use tokio_util::codec::{Decoder, Encoder}; use types::{ - light_client_bootstrap::LightClientBootstrap, EthSpec, ForkContext, ForkName, Hash256, + LightClientBootstrap, EthSpec, ForkContext, ForkName, Hash256, SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockMerge, }; use unsigned_varint::codec::Uvi; diff --git a/beacon_node/lighthouse_network/src/rpc/methods.rs b/beacon_node/lighthouse_network/src/rpc/methods.rs index 5da595c3db7..ed302fc475e 100644 --- a/beacon_node/lighthouse_network/src/rpc/methods.rs +++ b/beacon_node/lighthouse_network/src/rpc/methods.rs @@ -13,7 +13,7 @@ use std::sync::Arc; use strum::IntoStaticStr; use superstruct::superstruct; use types::{ - light_client_bootstrap::LightClientBootstrap, Epoch, EthSpec, Hash256, SignedBeaconBlock, Slot, + LightClientBootstrap, Epoch, EthSpec, Hash256, SignedBeaconBlock, Slot, }; /// Maximum number of blocks in a single request. diff --git a/beacon_node/lighthouse_network/src/service/api_types.rs b/beacon_node/lighthouse_network/src/service/api_types.rs index 849a86f51ba..bd76b1beb13 100644 --- a/beacon_node/lighthouse_network/src/service/api_types.rs +++ b/beacon_node/lighthouse_network/src/service/api_types.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use libp2p::core::connection::ConnectionId; -use types::{light_client_bootstrap::LightClientBootstrap, EthSpec, SignedBeaconBlock}; +use types::{LightClientBootstrap, EthSpec, SignedBeaconBlock}; use crate::rpc::{ methods::{ diff --git a/beacon_node/network/src/beacon_processor/worker/rpc_methods.rs b/beacon_node/network/src/beacon_processor/worker/rpc_methods.rs index bfa0ea516fa..7523d584351 100644 --- a/beacon_node/network/src/beacon_processor/worker/rpc_methods.rs +++ b/beacon_node/network/src/beacon_processor/worker/rpc_methods.rs @@ -11,7 +11,7 @@ use slog::{debug, error}; use slot_clock::SlotClock; use std::sync::Arc; use task_executor::TaskExecutor; -use types::{light_client_bootstrap::LightClientBootstrap, Epoch, EthSpec, Hash256, Slot}; +use types::{LightClientBootstrap, Epoch, EthSpec, Hash256, Slot}; use super::Worker; diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index 87f5ebe8b3c..c803c0e5192 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -139,6 +139,7 @@ pub use crate::free_attestation::FreeAttestation; pub use crate::graffiti::{Graffiti, GRAFFITI_BYTES_LEN}; pub use crate::historical_batch::HistoricalBatch; pub use crate::indexed_attestation::IndexedAttestation; +pub use crate::light_client_bootstrap::LightClientBootstrap; pub use crate::light_client_finality_update::LightClientFinalityUpdate; pub use crate::light_client_optimistic_update::LightClientOptimisticUpdate; pub use crate::participation_flags::ParticipationFlags; From 4611a9c21cf7cd73a62fda45edefc8c337eba0f1 Mon Sep 17 00:00:00 2001 From: geemo Date: Wed, 8 Feb 2023 20:18:11 -0600 Subject: [PATCH 10/72] add lc optimistic/finality update to events --- beacon_node/beacon_chain/src/events.rs | 18 ++++++++++++++++++ beacon_node/http_api/src/lib.rs | 6 ++++++ common/eth2/src/types.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/beacon_node/beacon_chain/src/events.rs b/beacon_node/beacon_chain/src/events.rs index 6f4415ef4f3..a0075efd24e 100644 --- a/beacon_node/beacon_chain/src/events.rs +++ b/beacon_node/beacon_chain/src/events.rs @@ -15,6 +15,8 @@ pub struct ServerSentEventHandler { chain_reorg_tx: Sender>, contribution_tx: Sender>, late_head: Sender>, + light_client_finality_update_tx: Sender>, + light_client_optimistic_update_tx: Sender>, block_reward_tx: Sender>, log: Logger, } @@ -33,6 +35,8 @@ impl ServerSentEventHandler { let (chain_reorg_tx, _) = broadcast::channel(capacity); let (contribution_tx, _) = broadcast::channel(capacity); let (late_head, _) = broadcast::channel(capacity); + let (light_client_finality_update_tx, _) = broadcast::channel(capacity); + let (light_client_optimistic_update_tx, _) = broadcast::channel(capacity); let (block_reward_tx, _) = broadcast::channel(capacity); Self { @@ -44,6 +48,8 @@ impl ServerSentEventHandler { chain_reorg_tx, contribution_tx, late_head, + light_client_finality_update_tx, + light_client_optimistic_update_tx, block_reward_tx, log, } @@ -70,6 +76,10 @@ impl ServerSentEventHandler { .map(|count| trace!(self.log, "Registering server-sent contribution and proof event"; "receiver_count" => count)), EventKind::LateHead(late_head) => self.late_head.send(EventKind::LateHead(late_head)) .map(|count| trace!(self.log, "Registering server-sent late head event"; "receiver_count" => count)), + EventKind::LightClientFinalityUpdate(update) => self.light_client_finality_update_tx.send(EventKind::LightClientFinalityUpdate(update)) + .map(|count| trace!(self.log, "Registering server-sent light client finality update event"; "receiver_count" => count)), + EventKind::LightClientOptimisticUpdate(update) => self.light_client_optimistic_update_tx.send(EventKind::LightClientOptimisticUpdate(update)) + .map(|count| trace!(self.log, "Registering server-sent light client optimistic update event"; "receiver_count" => count)), EventKind::BlockReward(block_reward) => self.block_reward_tx.send(EventKind::BlockReward(block_reward)) .map(|count| trace!(self.log, "Registering server-sent contribution and proof event"; "receiver_count" => count)), }; @@ -110,6 +120,14 @@ impl ServerSentEventHandler { self.late_head.subscribe() } + pub fn subscribe_light_client_finality_update(&self) -> Receiver> { + self.light_client_finality_update_tx.subscribe() + } + + pub fn subscribe_light_client_optimistic_update(&self) -> Receiver> { + self.light_client_optimistic_update_tx.subscribe() + } + pub fn subscribe_block_reward(&self) -> Receiver> { self.block_reward_tx.subscribe() } diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 091d81b79d3..7df8a9c7e4f 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -3567,6 +3567,12 @@ pub fn serve( api_types::EventTopic::LateHead => { event_handler.subscribe_late_head() } + api_types::EventTopic::LightClientFinalityUpdate => { + event_handler.subscribe_light_client_finality_update() + } + api_types::EventTopic::LightClientOptimisticUpdate => { + event_handler.subscribe_light_client_optimistic_update() + } api_types::EventTopic::BlockReward => { event_handler.subscribe_block_reward() } diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index 53cca49120a..788cbc5309d 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -923,6 +923,8 @@ pub enum EventKind { ChainReorg(SseChainReorg), ContributionAndProof(Box>), LateHead(SseLateHead), + LightClientFinalityUpdate(Box>), + LightClientOptimisticUpdate(Box>), #[cfg(feature = "lighthouse")] BlockReward(BlockReward), } @@ -938,6 +940,8 @@ impl EventKind { EventKind::ChainReorg(_) => "chain_reorg", EventKind::ContributionAndProof(_) => "contribution_and_proof", EventKind::LateHead(_) => "late_head", + EventKind::LightClientFinalityUpdate(_) => "light_client_finality_update", + EventKind::LightClientOptimisticUpdate(_) => "light_client_optimistic_update", #[cfg(feature = "lighthouse")] EventKind::BlockReward(_) => "block_reward", } @@ -992,6 +996,22 @@ impl EventKind { ServerError::InvalidServerSentEvent(format!("Contribution and Proof: {:?}", e)) })?, ))), + "light_client_finality_update" => Ok(EventKind::LightClientFinalityUpdate( + serde_json::from_str(data).map_err(|e| { + ServerError::InvalidServerSentEvent(format!( + "Light Client Finality Update: {:?}", + e + )) + })?, + )), + "light_client_optimistic_update" => Ok(EventKind::LightClientOptimisticUpdate( + serde_json::from_str(data).map_err(|e| { + ServerError::InvalidServerSentEvent(format!( + "Light Client Optimistic Update: {:?}", + e + )) + })?, + )), #[cfg(feature = "lighthouse")] "block_reward" => Ok(EventKind::BlockReward(serde_json::from_str(data).map_err( |e| ServerError::InvalidServerSentEvent(format!("Block Reward: {:?}", e)), @@ -1021,6 +1041,8 @@ pub enum EventTopic { ChainReorg, ContributionAndProof, LateHead, + LightClientFinalityUpdate, + LightClientOptimisticUpdate, #[cfg(feature = "lighthouse")] BlockReward, } @@ -1038,6 +1060,8 @@ impl FromStr for EventTopic { "chain_reorg" => Ok(EventTopic::ChainReorg), "contribution_and_proof" => Ok(EventTopic::ContributionAndProof), "late_head" => Ok(EventTopic::LateHead), + "light_client_finality_update" => Ok(EventTopic::LightClientFinalityUpdate), + "light_client_optimistic_update" => Ok(EventTopic::LightClientOptimisticUpdate), #[cfg(feature = "lighthouse")] "block_reward" => Ok(EventTopic::BlockReward), _ => Err("event topic cannot be parsed.".to_string()), @@ -1056,6 +1080,8 @@ impl fmt::Display for EventTopic { EventTopic::ChainReorg => write!(f, "chain_reorg"), EventTopic::ContributionAndProof => write!(f, "contribution_and_proof"), EventTopic::LateHead => write!(f, "late_head"), + EventTopic::LightClientFinalityUpdate => write!(f, "light_client_finality_update"), + EventTopic::LightClientOptimisticUpdate => write!(f, "light_client_optimistic_update"), #[cfg(feature = "lighthouse")] EventTopic::BlockReward => write!(f, "block_reward"), } From d3da99fa748a6eb50e8f76701d5f090a72721494 Mon Sep 17 00:00:00 2001 From: geemo Date: Wed, 8 Feb 2023 20:30:06 -0600 Subject: [PATCH 11/72] fmt --- beacon_node/http_api/src/lib.rs | 49 ++++++++++--------- .../src/rpc/codec/ssz_snappy.rs | 4 +- .../lighthouse_network/src/rpc/methods.rs | 4 +- .../src/service/api_types.rs | 2 +- .../beacon_processor/worker/rpc_methods.rs | 2 +- 5 files changed, 32 insertions(+), 29 deletions(-) diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 7df8a9c7e4f..698e5715f71 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -54,11 +54,11 @@ use tokio::sync::mpsc::{Sender, UnboundedSender}; use tokio_stream::{wrappers::BroadcastStream, StreamExt}; use types::{ Attestation, AttestationData, AttesterSlashing, BeaconStateError, BlindedPayload, - CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName, FullPayload, - ProposerPreparationData, ProposerSlashing, RelativeEpoch, SignedAggregateAndProof, - SignedBeaconBlock, SignedBlindedBeaconBlock, SignedContributionAndProof, - SignedValidatorRegistrationData, SignedVoluntaryExit, Slot, SyncCommitteeMessage, - SyncContributionData, Hash256, LightClientBootstrap, + CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName, FullPayload, Hash256, + LightClientBootstrap, ProposerPreparationData, ProposerSlashing, RelativeEpoch, + SignedAggregateAndProof, SignedBeaconBlock, SignedBlindedBeaconBlock, + SignedContributionAndProof, SignedValidatorRegistrationData, SignedVoluntaryExit, Slot, + SyncCommitteeMessage, SyncContributionData, }; use version::{ add_consensus_version_header, execution_optimistic_fork_versioned_response, @@ -1758,8 +1758,8 @@ pub fn serve( .and(warp::header::optional::("accept")) .and_then( |chain: Arc>, - block_root: Hash256, - accept_header: Option| { + block_root: Hash256, + accept_header: Option| { blocking_task(move || { let state_root = chain .get_blinded_block(&block_root) @@ -1775,19 +1775,23 @@ pub fn serve( ) })?; - let mut state = chain.get_state(&state_root, None).map_err(|_| { - warp_utils::reject::custom_server_error( - "Error retrieving state".to_string(), - ) - })? - .ok_or_else(|| { - warp_utils::reject::custom_not_found("Light client bootstrap unavailable".to_string()) - })?; + let mut state = chain + .get_state(&state_root, None) + .map_err(|_| { + warp_utils::reject::custom_server_error( + "Error retrieving state".to_string(), + ) + })? + .ok_or_else(|| { + warp_utils::reject::custom_not_found( + "Light client bootstrap unavailable".to_string(), + ) + })?; let fork_name = state .fork_name(&chain.spec) .map_err(inconsistent_fork_rejection)?; - let bootstrap = LightClientBootstrap::from_beacon_state(&mut state) - .map_err(|_| { + let bootstrap = + LightClientBootstrap::from_beacon_state(&mut state).map_err(|_| { warp_utils::reject::custom_server_error( "Failed to create light client bootstrap".to_string(), ) @@ -1803,15 +1807,16 @@ pub fn serve( e )) }), - _ => Ok(warp::reply::json(&api_types::GenericResponse::from(bootstrap)) - .into_response()), + _ => Ok( + warp::reply::json(&api_types::GenericResponse::from(bootstrap)) + .into_response(), + ), } .map(|resp| add_consensus_version_header(resp, fork_name)) - }) - } + }) + }, ); - // GET beacon/light_client/optimistic_update let get_beacon_light_client_optimistic_update = beacon_light_client_path .clone() diff --git a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs index 96b0cd26eb6..1481e38c6a7 100644 --- a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs +++ b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs @@ -16,8 +16,8 @@ use std::marker::PhantomData; use std::sync::Arc; use tokio_util::codec::{Decoder, Encoder}; use types::{ - LightClientBootstrap, EthSpec, ForkContext, ForkName, Hash256, - SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockMerge, + EthSpec, ForkContext, ForkName, Hash256, LightClientBootstrap, SignedBeaconBlock, + SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockMerge, }; use unsigned_varint::codec::Uvi; diff --git a/beacon_node/lighthouse_network/src/rpc/methods.rs b/beacon_node/lighthouse_network/src/rpc/methods.rs index ed302fc475e..47963c7000d 100644 --- a/beacon_node/lighthouse_network/src/rpc/methods.rs +++ b/beacon_node/lighthouse_network/src/rpc/methods.rs @@ -12,9 +12,7 @@ use std::ops::Deref; use std::sync::Arc; use strum::IntoStaticStr; use superstruct::superstruct; -use types::{ - LightClientBootstrap, Epoch, EthSpec, Hash256, SignedBeaconBlock, Slot, -}; +use types::{Epoch, EthSpec, Hash256, LightClientBootstrap, SignedBeaconBlock, Slot}; /// Maximum number of blocks in a single request. pub type MaxRequestBlocks = U1024; diff --git a/beacon_node/lighthouse_network/src/service/api_types.rs b/beacon_node/lighthouse_network/src/service/api_types.rs index bd76b1beb13..cc524ea4b60 100644 --- a/beacon_node/lighthouse_network/src/service/api_types.rs +++ b/beacon_node/lighthouse_network/src/service/api_types.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use libp2p::core::connection::ConnectionId; -use types::{LightClientBootstrap, EthSpec, SignedBeaconBlock}; +use types::{EthSpec, LightClientBootstrap, SignedBeaconBlock}; use crate::rpc::{ methods::{ diff --git a/beacon_node/network/src/beacon_processor/worker/rpc_methods.rs b/beacon_node/network/src/beacon_processor/worker/rpc_methods.rs index 7523d584351..b490ff0170e 100644 --- a/beacon_node/network/src/beacon_processor/worker/rpc_methods.rs +++ b/beacon_node/network/src/beacon_processor/worker/rpc_methods.rs @@ -11,7 +11,7 @@ use slog::{debug, error}; use slot_clock::SlotClock; use std::sync::Arc; use task_executor::TaskExecutor; -use types::{LightClientBootstrap, Epoch, EthSpec, Hash256, Slot}; +use types::{Epoch, EthSpec, Hash256, LightClientBootstrap, Slot}; use super::Worker; From a2f6a8ac52a7aaf50894bec3ada0466a5df23870 Mon Sep 17 00:00:00 2001 From: geemo Date: Fri, 10 Feb 2023 23:53:41 -0600 Subject: [PATCH 12/72] That error isn't occuring on my computer but I think this should fix it --- beacon_node/tests/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/tests/test.rs b/beacon_node/tests/test.rs index 1c11a8349dd..8ccb260d29b 100644 --- a/beacon_node/tests/test.rs +++ b/beacon_node/tests/test.rs @@ -1,5 +1,5 @@ #![cfg(test)] -#![recursion_limit = "256"] +#![recursion_limit = "512"] use beacon_chain::StateSkipConfig; use node_test_rig::{ From d74e3b46b179e55034e28b1a0364ddd7fcd4cc5a Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Thu, 16 Nov 2023 12:05:05 +1100 Subject: [PATCH 13/72] Add missing test file --- .../fixtures/mainnet/test_blobs_bundle.ssz | Bin 0 -> 131180 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 beacon_node/execution_layer/src/test_utils/fixtures/mainnet/test_blobs_bundle.ssz 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..6b549a4da84f33f1feea50ab073da9078c18bd6d GIT binary patch literal 131180 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�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 Date: Thu, 16 Nov 2023 16:34:02 +1100 Subject: [PATCH 14/72] Update light client types to comply with Altair light client spec. --- ...ght_client_finality_update_verification.rs | 4 +- ...t_client_optimistic_update_verification.rs | 5 ++- .../lighthouse_network/src/rpc/methods.rs | 6 ++- common/eth2/src/lib.rs | 29 +++++++++++--- .../state_processing/src/upgrade/altair.rs | 2 +- consensus/types/src/lib.rs | 2 + consensus/types/src/light_client_bootstrap.rs | 34 +++++++++++++--- .../types/src/light_client_finality_update.rs | 39 +++++++++++++++---- consensus/types/src/light_client_header.rs | 26 +++++++++++++ .../src/light_client_optimistic_update.rs | 30 ++++++++++++-- consensus/types/src/light_client_update.rs | 34 +++++++++++++--- consensus/types/src/sync_committee.rs | 12 ++---- 12 files changed, 180 insertions(+), 43 deletions(-) create mode 100644 consensus/types/src/light_client_header.rs diff --git a/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs b/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs index 86cc6695edb..791d63ccfe5 100644 --- a/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs +++ b/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs @@ -67,7 +67,7 @@ impl VerifiedLightClientFinalityUpdate { chain: &BeaconChain, seen_timestamp: Duration, ) -> Result { - let gossiped_finality_slot = light_client_finality_update.finalized_header.slot; + let gossiped_finality_slot = light_client_finality_update.finalized_header.beacon.slot; let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0); let signature_slot = light_client_finality_update.signature_slot; let start_time = chain.slot_clock.start_of(signature_slot); @@ -88,7 +88,7 @@ impl VerifiedLightClientFinalityUpdate { .get_blinded_block(&finalized_block_root)? .ok_or(Error::FailedConstructingUpdate)?; let latest_seen_finality_update_slot = match latest_seen_finality_update.as_ref() { - Some(update) => update.finalized_header.slot, + Some(update) => update.finalized_header.beacon.slot, None => Slot::new(0), }; diff --git a/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs b/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs index 8b7e6445273..374cc9a7753 100644 --- a/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs +++ b/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs @@ -71,7 +71,7 @@ impl VerifiedLightClientOptimisticUpdate { chain: &BeaconChain, seen_timestamp: Duration, ) -> Result { - let gossiped_optimistic_slot = light_client_optimistic_update.attested_header.slot; + let gossiped_optimistic_slot = light_client_optimistic_update.attested_header.beacon.slot; let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0); let signature_slot = light_client_optimistic_update.signature_slot; let start_time = chain.slot_clock.start_of(signature_slot); @@ -88,7 +88,7 @@ impl VerifiedLightClientOptimisticUpdate { .get_state(&attested_block.state_root(), Some(attested_block.slot()))? .ok_or(Error::FailedConstructingUpdate)?; let latest_seen_optimistic_update_slot = match latest_seen_optimistic_update.as_ref() { - Some(update) => update.attested_header.slot, + Some(update) => update.attested_header.beacon.slot, None => Slot::new(0), }; @@ -114,6 +114,7 @@ impl VerifiedLightClientOptimisticUpdate { // otherwise queue let canonical_root = light_client_optimistic_update .attested_header + .beacon .canonical_root(); if canonical_root != head_block.message().parent_root() { diff --git a/beacon_node/lighthouse_network/src/rpc/methods.rs b/beacon_node/lighthouse_network/src/rpc/methods.rs index a293de4e9bd..627c871c471 100644 --- a/beacon_node/lighthouse_network/src/rpc/methods.rs +++ b/beacon_node/lighthouse_network/src/rpc/methods.rs @@ -571,7 +571,11 @@ impl std::fmt::Display for RPCResponse { RPCResponse::Pong(ping) => write!(f, "Pong: {}", ping.data), RPCResponse::MetaData(metadata) => write!(f, "Metadata: {}", metadata.seq_number()), RPCResponse::LightClientBootstrap(bootstrap) => { - write!(f, "LightClientBootstrap Slot: {}", bootstrap.header.slot) + write!( + f, + "LightClientBootstrap Slot: {}", + bootstrap.header.beacon.slot + ) } } } diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 9436d303ac6..7ed1c5c540c 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -667,12 +667,31 @@ impl BeaconNodeHttpClient { self.get_opt(path).await } - /// `GET beacon/light_client/optimimistic_update` + /// `GET beacon/light_client/bootstrap` /// /// Returns `Ok(None)` on a 404 error. - pub async fn get_beacon_light_client_optimistic_update( + pub async fn get_light_client_bootstrap( &self, - ) -> Result>, Error> { + block_root: Hash256, + ) -> Result>>, Error> { + let mut path = self.eth_path(V1)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("beacon") + .push("light_client") + .push("bootstrap") + .push(&format!("{:?}", block_root)); + + self.get_opt(path).await + } + + /// `GET beacon/light_client/optimistic_update` + /// + /// Returns `Ok(None)` on a 404 error. + pub async fn get_beacon_light_client_optimistic_update( + &self, + ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() @@ -687,9 +706,9 @@ impl BeaconNodeHttpClient { /// `GET beacon/light_client/finality_update` /// /// Returns `Ok(None)` on a 404 error. - pub async fn get_beacon_light_client_finality_update( + pub async fn get_beacon_light_client_finality_update( &self, - ) -> Result>, Error> { + ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() diff --git a/consensus/state_processing/src/upgrade/altair.rs b/consensus/state_processing/src/upgrade/altair.rs index 26b1192bc16..5bb4f0bd592 100644 --- a/consensus/state_processing/src/upgrade/altair.rs +++ b/consensus/state_processing/src/upgrade/altair.rs @@ -54,7 +54,7 @@ pub fn upgrade_to_altair( VariableList::new(vec![ParticipationFlags::default(); pre.validators.len()])?; let inactivity_scores = VariableList::new(vec![0; pre.validators.len()])?; - let temp_sync_committee = Arc::new(SyncCommittee::temporary()?); + let temp_sync_committee = Arc::new(SyncCommittee::temporary()); // Where possible, use something like `mem::take` to move fields from behind the &mut // reference. For other fields that don't have a good default value, use `clone`. diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index 52300df3fd4..262cd24f9a8 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -99,6 +99,7 @@ pub mod slot_data; pub mod sqlite; pub mod blob_sidecar; +pub mod light_client_header; pub mod sidecar; pub mod signed_blob; @@ -156,6 +157,7 @@ pub use crate::historical_batch::HistoricalBatch; pub use crate::indexed_attestation::IndexedAttestation; pub use crate::light_client_bootstrap::LightClientBootstrap; pub use crate::light_client_finality_update::LightClientFinalityUpdate; +pub use crate::light_client_header::LightClientHeader; pub use crate::light_client_optimistic_update::LightClientOptimisticUpdate; pub use crate::participation_flags::ParticipationFlags; pub use crate::participation_list::ParticipationList; diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 1d70456d732..15cc564c6e7 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -1,6 +1,10 @@ -use super::{BeaconBlockHeader, BeaconState, EthSpec, FixedVector, Hash256, SyncCommittee}; -use crate::{light_client_update::*, test_utils::TestRandom}; -use serde::{Deserialize, Serialize}; +use super::{BeaconState, EthSpec, FixedVector, Hash256, SyncCommittee}; +use crate::{ + light_client_update::*, test_utils::TestRandom, ForkName, ForkVersionDeserialize, + LightClientHeader, +}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_json::Value; use ssz_derive::{Decode, Encode}; use std::sync::Arc; use test_random_derive::TestRandom; @@ -22,8 +26,8 @@ use tree_hash::TreeHash; #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] pub struct LightClientBootstrap { - /// Requested beacon block header. - pub header: BeaconBlockHeader, + /// The requested beacon block header. + pub header: LightClientHeader, /// The `SyncCommittee` used in the requested period. pub current_sync_committee: Arc>, /// Merkle proof for sync committee @@ -37,13 +41,31 @@ impl LightClientBootstrap { let current_sync_committee_branch = beacon_state.compute_merkle_proof(CURRENT_SYNC_COMMITTEE_INDEX)?; Ok(LightClientBootstrap { - header, + header: header.into(), current_sync_committee: beacon_state.current_sync_committee()?.clone(), current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, }) } } +impl ForkVersionDeserialize for LightClientBootstrap { + fn deserialize_by_fork<'de, D: Deserializer<'de>>( + value: Value, + fork_name: ForkName, + ) -> Result { + match fork_name { + ForkName::Altair => Ok(serde_json::from_value::>(value) + .map_err(serde::de::Error::custom))?, + ForkName::Base | ForkName::Merge | ForkName::Capella | ForkName::Deneb => { + Err(serde::de::Error::custom(format!( + "LightClientBootstrap failed to deserialize: unsupported fork '{}'", + fork_name + ))) + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index 30f337fb2bb..49c2db28b90 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -1,9 +1,12 @@ use super::{ - BeaconBlockHeader, EthSpec, FixedVector, Hash256, SignedBeaconBlock, SignedBlindedBeaconBlock, - Slot, SyncAggregate, + EthSpec, FixedVector, Hash256, SignedBeaconBlock, SignedBlindedBeaconBlock, Slot, SyncAggregate, }; -use crate::{light_client_update::*, test_utils::TestRandom, BeaconState, ChainSpec}; -use serde::{Deserialize, Serialize}; +use crate::{ + light_client_update::*, test_utils::TestRandom, BeaconState, ChainSpec, ForkName, + ForkVersionDeserialize, LightClientHeader, +}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_json::Value; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash::TreeHash; @@ -25,9 +28,9 @@ use tree_hash::TreeHash; #[arbitrary(bound = "T: EthSpec")] pub struct LightClientFinalityUpdate { /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: BeaconBlockHeader, + pub attested_header: LightClientHeader, /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). - pub finalized_header: BeaconBlockHeader, + pub finalized_header: LightClientHeader, /// Merkle proof attesting finalized header. pub finality_branch: FixedVector, /// current sync aggreggate @@ -68,8 +71,8 @@ impl LightClientFinalityUpdate { let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; Ok(Self { - attested_header, - finalized_header, + attested_header: attested_header.into(), + finalized_header: finalized_header.into(), finality_branch: FixedVector::new(finality_branch)?, sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), @@ -77,6 +80,26 @@ impl LightClientFinalityUpdate { } } +impl ForkVersionDeserialize for LightClientFinalityUpdate { + fn deserialize_by_fork<'de, D: Deserializer<'de>>( + value: Value, + fork_name: ForkName, + ) -> Result { + match fork_name { + ForkName::Altair => Ok( + serde_json::from_value::>(value) + .map_err(serde::de::Error::custom), + )?, + ForkName::Base | ForkName::Merge | ForkName::Capella | ForkName::Deneb => { + Err(serde::de::Error::custom(format!( + "LightClientFinalityUpdate failed to deserialize: unsupported fork '{}'", + fork_name + ))) + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs new file mode 100644 index 00000000000..8fe31f7af8c --- /dev/null +++ b/consensus/types/src/light_client_header.rs @@ -0,0 +1,26 @@ +use crate::test_utils::TestRandom; +use crate::BeaconBlockHeader; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; + +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + TestRandom, + arbitrary::Arbitrary, +)] +pub struct LightClientHeader { + pub beacon: BeaconBlockHeader, +} + +impl From for LightClientHeader { + fn from(beacon: BeaconBlockHeader) -> Self { + LightClientHeader { beacon } + } +} diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index fbb0558eced..3a0f1d536c8 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -1,8 +1,10 @@ -use super::{BeaconBlockHeader, EthSpec, Slot, SyncAggregate}; +use super::{EthSpec, ForkName, ForkVersionDeserialize, Slot, SyncAggregate}; +use crate::light_client_header::LightClientHeader; use crate::{ light_client_update::Error, test_utils::TestRandom, BeaconState, ChainSpec, SignedBeaconBlock, }; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_json::Value; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash::TreeHash; @@ -24,7 +26,7 @@ use tree_hash::TreeHash; #[arbitrary(bound = "T: EthSpec")] pub struct LightClientOptimisticUpdate { /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: BeaconBlockHeader, + pub attested_header: LightClientHeader, /// current sync aggreggate pub sync_aggregate: SyncAggregate, /// Slot of the sync aggregated singature @@ -53,13 +55,33 @@ impl LightClientOptimisticUpdate { let mut attested_header = attested_state.latest_block_header().clone(); attested_header.state_root = attested_state.tree_hash_root(); Ok(Self { - attested_header, + attested_header: attested_header.into(), sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), }) } } +impl ForkVersionDeserialize for LightClientOptimisticUpdate { + fn deserialize_by_fork<'de, D: Deserializer<'de>>( + value: Value, + fork_name: ForkName, + ) -> Result { + match fork_name { + ForkName::Altair => Ok(serde_json::from_value::>( + value, + ) + .map_err(serde::de::Error::custom))?, + ForkName::Base | ForkName::Merge | ForkName::Capella | ForkName::Deneb => { + Err(serde::de::Error::custom(format!( + "LightClientOptimisticUpdate failed to deserialize: unsupported fork '{}'", + fork_name + ))) + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 6e53e14c994..6fb98427267 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -1,7 +1,11 @@ use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee}; -use crate::{beacon_state, test_utils::TestRandom, BeaconBlock, BeaconState, ChainSpec}; +use crate::{ + beacon_state, test_utils::TestRandom, BeaconBlock, BeaconState, ChainSpec, ForkName, + ForkVersionDeserialize, LightClientHeader, +}; use safe_arith::ArithError; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_json::Value; use ssz_derive::{Decode, Encode}; use ssz_types::typenum::{U5, U6}; use std::sync::Arc; @@ -67,13 +71,13 @@ impl From for Error { #[arbitrary(bound = "T: EthSpec")] pub struct LightClientUpdate { /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: BeaconBlockHeader, + pub attested_header: LightClientHeader, /// The `SyncCommittee` used in the next period. pub next_sync_committee: Arc>, /// Merkle proof for next sync committee pub next_sync_committee_branch: FixedVector, /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). - pub finalized_header: BeaconBlockHeader, + pub finalized_header: LightClientHeader, /// Merkle proof attesting finalized header. pub finality_branch: FixedVector, /// current sync aggreggate @@ -128,10 +132,10 @@ impl LightClientUpdate { attested_state.compute_merkle_proof(NEXT_SYNC_COMMITTEE_INDEX)?; let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; Ok(Self { - attested_header, + attested_header: attested_header.into(), next_sync_committee: attested_state.next_sync_committee()?.clone(), next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?, - finalized_header, + finalized_header: finalized_header.into(), finality_branch: FixedVector::new(finality_branch)?, sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), @@ -139,6 +143,24 @@ impl LightClientUpdate { } } +impl ForkVersionDeserialize for LightClientUpdate { + fn deserialize_by_fork<'de, D: Deserializer<'de>>( + value: Value, + fork_name: ForkName, + ) -> Result { + match fork_name { + ForkName::Altair => Ok(serde_json::from_value::>(value) + .map_err(serde::de::Error::custom))?, + ForkName::Base | ForkName::Merge | ForkName::Capella | ForkName::Deneb => { + Err(serde::de::Error::custom(format!( + "LightClientUpdate failed to deserialize: unsupported fork '{}'", + fork_name + ))) + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/consensus/types/src/sync_committee.rs b/consensus/types/src/sync_committee.rs index 0bcf505f257..b42a000bb00 100644 --- a/consensus/types/src/sync_committee.rs +++ b/consensus/types/src/sync_committee.rs @@ -1,5 +1,4 @@ use crate::test_utils::TestRandom; -use crate::typenum::Unsigned; use crate::{EthSpec, FixedVector, SyncSubnetId}; use bls::PublicKeyBytes; use safe_arith::{ArithError, SafeArith}; @@ -46,14 +45,11 @@ pub struct SyncCommittee { impl SyncCommittee { /// Create a temporary sync committee that should *never* be included in a legitimate consensus object. - pub fn temporary() -> Result { - Ok(Self { - pubkeys: FixedVector::new(vec![ - PublicKeyBytes::empty(); - T::SyncCommitteeSize::to_usize() - ])?, + pub fn temporary() -> Self { + Self { + pubkeys: FixedVector::from_elem(PublicKeyBytes::empty()), aggregate_pubkey: PublicKeyBytes::empty(), - }) + } } /// Return the pubkeys in this `SyncCommittee` for the given `subcommittee_index`. From f062e774acc412f18e881b61c0d52b688dc545b7 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Thu, 16 Nov 2023 21:52:43 +1100 Subject: [PATCH 15/72] Fix test compilation --- beacon_node/http_api/tests/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 5e5212b0cbc..8e049c5e387 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -1651,7 +1651,7 @@ impl ApiTester { .get_beacon_light_client_optimistic_update::() .await { - Ok(result) => result, + Ok(result) => result.map(|res| res.data), Err(_) => panic!("query did not fail correctly"), }; @@ -1667,7 +1667,7 @@ impl ApiTester { .get_beacon_light_client_finality_update::() .await { - Ok(result) => result, + Ok(result) => result.map(|res| res.data), Err(_) => panic!("query did not fail correctly"), }; From bfd3fb7b312aa7f1f63cdc8ab22ec2181fd0a26f Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Fri, 17 Nov 2023 13:32:38 +1100 Subject: [PATCH 16/72] Support deserializing light client structures for the Bellatrix fork --- consensus/types/src/light_client_bootstrap.rs | 8 +++++--- consensus/types/src/light_client_finality_update.rs | 10 +++++----- consensus/types/src/light_client_optimistic_update.rs | 8 ++++---- consensus/types/src/light_client_update.rs | 8 +++++--- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 15cc564c6e7..14bed794633 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -54,9 +54,11 @@ impl ForkVersionDeserialize for LightClientBootstrap { fork_name: ForkName, ) -> Result { match fork_name { - ForkName::Altair => Ok(serde_json::from_value::>(value) - .map_err(serde::de::Error::custom))?, - ForkName::Base | ForkName::Merge | ForkName::Capella | ForkName::Deneb => { + ForkName::Altair | ForkName::Merge => { + Ok(serde_json::from_value::>(value) + .map_err(serde::de::Error::custom))? + } + ForkName::Base | ForkName::Capella | ForkName::Deneb => { Err(serde::de::Error::custom(format!( "LightClientBootstrap failed to deserialize: unsupported fork '{}'", fork_name diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index 49c2db28b90..87601b81565 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -86,11 +86,11 @@ impl ForkVersionDeserialize for LightClientFinalityUpdate { fork_name: ForkName, ) -> Result { match fork_name { - ForkName::Altair => Ok( - serde_json::from_value::>(value) - .map_err(serde::de::Error::custom), - )?, - ForkName::Base | ForkName::Merge | ForkName::Capella | ForkName::Deneb => { + ForkName::Altair | ForkName::Merge => Ok(serde_json::from_value::< + LightClientFinalityUpdate, + >(value) + .map_err(serde::de::Error::custom))?, + ForkName::Base | ForkName::Capella | ForkName::Deneb => { Err(serde::de::Error::custom(format!( "LightClientFinalityUpdate failed to deserialize: unsupported fork '{}'", fork_name diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index 3a0f1d536c8..d883d735f35 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -68,11 +68,11 @@ impl ForkVersionDeserialize for LightClientOptimisticUpdate { fork_name: ForkName, ) -> Result { match fork_name { - ForkName::Altair => Ok(serde_json::from_value::>( - value, - ) + ForkName::Altair | ForkName::Merge => Ok(serde_json::from_value::< + LightClientOptimisticUpdate, + >(value) .map_err(serde::de::Error::custom))?, - ForkName::Base | ForkName::Merge | ForkName::Capella | ForkName::Deneb => { + ForkName::Base | ForkName::Capella | ForkName::Deneb => { Err(serde::de::Error::custom(format!( "LightClientOptimisticUpdate failed to deserialize: unsupported fork '{}'", fork_name diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 6fb98427267..718cd7553f9 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -149,9 +149,11 @@ impl ForkVersionDeserialize for LightClientUpdate { fork_name: ForkName, ) -> Result { match fork_name { - ForkName::Altair => Ok(serde_json::from_value::>(value) - .map_err(serde::de::Error::custom))?, - ForkName::Base | ForkName::Merge | ForkName::Capella | ForkName::Deneb => { + ForkName::Altair | ForkName::Merge => { + Ok(serde_json::from_value::>(value) + .map_err(serde::de::Error::custom))? + } + ForkName::Base | ForkName::Capella | ForkName::Deneb => { Err(serde::de::Error::custom(format!( "LightClientUpdate failed to deserialize: unsupported fork '{}'", fork_name From d90df3f103fe68e9b49d7a81fd04a70e4b3519c1 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Fri, 17 Nov 2023 15:02:13 +1100 Subject: [PATCH 17/72] Move `get_light_client_bootstrap` logic to `BeaconChain`. `LightClientBootstrap` API to return `ForkVersionedResponse`. --- beacon_node/beacon_chain/src/beacon_chain.rs | 27 +++++++ beacon_node/beacon_chain/src/errors.rs | 1 + beacon_node/http_api/src/lib.rs | 59 +++++---------- .../network_beacon_processor/rpc_methods.rs | 74 +++++-------------- consensus/types/src/lib.rs | 1 + 5 files changed, 68 insertions(+), 94 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 2fd70056cc1..c08a1e227fb 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -6446,6 +6446,33 @@ impl BeaconChain { pub fn data_availability_boundary(&self) -> Option { self.data_availability_checker.data_availability_boundary() } + + /// Gets the `LightClientBootstrap` object for a requested block root. + /// + /// Returns `None` when the state or block is not found in the database. + pub fn get_light_client_bootstrap( + &self, + block_root: &Hash256, + ) -> Result, ForkName)>, Error> { + let Some(state_root) = self + .get_blinded_block(block_root)? + .map(|block| block.state_root()) + else { + return Ok(None); + }; + + let Some(mut state) = self.get_state(&state_root, None)? else { + return Ok(None); + }; + + let fork_name = state + .fork_name(&self.spec) + .map_err(Error::InconsistentFork)?; + + LightClientBootstrap::from_beacon_state(&mut state) + .map(|bootstrap| Some((bootstrap, fork_name))) + .map_err(Error::LightClientError) + } } impl Drop for BeaconChain { diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 7c1bb04917d..8e6018c72b0 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -221,6 +221,7 @@ pub enum BeaconChainError { ProposerHeadForkChoiceError(fork_choice::Error), UnableToPublish, AvailabilityCheckError(AvailabilityCheckError), + LightClientError(LightClientError), } easy_from_to!(SlotProcessingError, BeaconChainError); diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 63af4309981..11984bb88d0 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -76,8 +76,8 @@ use tokio_stream::{ }; use types::{ Attestation, AttestationData, AttestationShufflingId, AttesterSlashing, BeaconStateError, - BlindedPayload, CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName, Hash256, - LightClientBootstrap, ProposerPreparationData, ProposerSlashing, RelativeEpoch, + BlindedPayload, CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName, + ForkVersionedResponse, Hash256, ProposerPreparationData, ProposerSlashing, RelativeEpoch, SignedAggregateAndProof, SignedBlsToExecutionChange, SignedContributionAndProof, SignedValidatorRegistrationData, SignedVoluntaryExit, Slot, SyncCommitteeMessage, SyncContributionData, @@ -2423,41 +2423,21 @@ pub fn serve( block_root: Hash256, accept_header: Option| { task_spawner.blocking_response_task(Priority::P1, move || { - let state_root = chain - .get_blinded_block(&block_root) - .map_err(|_| { - warp_utils::reject::custom_server_error( - "Error retrieving block".to_string(), - ) - })? - .map(|signed_block| signed_block.state_root()) - .ok_or_else(|| { - warp_utils::reject::custom_not_found( + let (bootstrap, fork_name) = match chain.get_light_client_bootstrap(&block_root) + { + Ok(Some(res)) => res, + Ok(None) => { + return Err(warp_utils::reject::custom_not_found( "Light client bootstrap unavailable".to_string(), - ) - })?; + )); + } + Err(e) => { + return Err(warp_utils::reject::custom_server_error(format!( + "Unable to obtain LightClientBootstrap instance: {e:?}" + ))); + } + }; - let mut state = chain - .get_state(&state_root, None) - .map_err(|_| { - warp_utils::reject::custom_server_error( - "Error retrieving state".to_string(), - ) - })? - .ok_or_else(|| { - warp_utils::reject::custom_not_found( - "Light client bootstrap unavailable".to_string(), - ) - })?; - let fork_name = state - .fork_name(&chain.spec) - .map_err(inconsistent_fork_rejection)?; - let bootstrap = - LightClientBootstrap::from_beacon_state(&mut state).map_err(|_| { - warp_utils::reject::custom_server_error( - "Failed to create light client bootstrap".to_string(), - ) - })?; match accept_header { Some(api_types::Accept::Ssz) => Response::builder() .status(200) @@ -2469,10 +2449,11 @@ pub fn serve( e )) }), - _ => Ok( - warp::reply::json(&api_types::GenericResponse::from(bootstrap)) - .into_response(), - ), + _ => Ok(warp::reply::json(&ForkVersionedResponse { + version: Some(fork_name), + data: bootstrap, + }) + .into_response()), } .map(|resp| add_consensus_version_header(resp, fork_name)) }) diff --git a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs index 0e6c76e222b..7210e08c621 100644 --- a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs @@ -18,9 +18,7 @@ use std::sync::Arc; use task_executor::TaskExecutor; use tokio_stream::StreamExt; use types::blob_sidecar::BlobIdentifier; -use types::{ - light_client_bootstrap::LightClientBootstrap, Epoch, EthSpec, ForkName, Hash256, Slot, -}; +use types::{Epoch, EthSpec, ForkName, Hash256, Slot}; impl NetworkBeaconProcessor { /* Auxiliary functions */ @@ -304,66 +302,32 @@ impl NetworkBeaconProcessor { request: LightClientBootstrapRequest, ) { let block_root = request.root; - let state_root = match self.chain.get_blinded_block(&block_root) { - Ok(signed_block) => match signed_block { - Some(signed_block) => signed_block.state_root(), - None => { - self.send_error_response( - peer_id, - RPCResponseErrorCode::ResourceUnavailable, - "Bootstrap not available".into(), - request_id, - ); - return; - } - }, - Err(_) => { - self.send_error_response( - peer_id, - RPCResponseErrorCode::ResourceUnavailable, - "Bootstrap not available".into(), - request_id, - ); - return; - } - }; - let mut beacon_state = match self.chain.get_state(&state_root, None) { - Ok(beacon_state) => match beacon_state { - Some(state) => state, - None => { - self.send_error_response( - peer_id, - RPCResponseErrorCode::ResourceUnavailable, - "Bootstrap not available".into(), - request_id, - ); - return; - } - }, - Err(_) => { + match self.chain.get_light_client_bootstrap(&block_root) { + Ok(Some((bootstrap, _))) => self.send_response( + peer_id, + Response::LightClientBootstrap(bootstrap), + request_id, + ), + Ok(None) => self.send_error_response( + peer_id, + RPCResponseErrorCode::ResourceUnavailable, + "Bootstrap not available".into(), + request_id, + ), + Err(e) => { self.send_error_response( peer_id, RPCResponseErrorCode::ResourceUnavailable, "Bootstrap not available".into(), request_id, ); - return; + error!(self.log, "Error getting LightClientBootstrap instance"; + "block_root" => ?block_root, + "peer" => %peer_id, + "error" => ?e + ) } }; - let Ok(bootstrap) = LightClientBootstrap::from_beacon_state(&mut beacon_state) else { - self.send_error_response( - peer_id, - RPCResponseErrorCode::ResourceUnavailable, - "Bootstrap not available".into(), - request_id, - ); - return; - }; - self.send_response( - peer_id, - Response::LightClientBootstrap(bootstrap), - request_id, - ) } /// Handle a `BlocksByRange` request from the peer. diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index 262cd24f9a8..0f284bde9d2 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -159,6 +159,7 @@ pub use crate::light_client_bootstrap::LightClientBootstrap; pub use crate::light_client_finality_update::LightClientFinalityUpdate; pub use crate::light_client_header::LightClientHeader; pub use crate::light_client_optimistic_update::LightClientOptimisticUpdate; +pub use crate::light_client_update::{Error as LightClientError, LightClientUpdate}; pub use crate::participation_flags::ParticipationFlags; pub use crate::participation_list::ParticipationList; pub use crate::payload::{ From 80ff555de54a0726cfd5624d8fcb7e2436e462b7 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Sat, 18 Nov 2023 01:48:46 +1100 Subject: [PATCH 18/72] Misc fixes. - log cleanup - move http_api config mutation to `config::get_config` for consistency - fix light client API responses --- beacon_node/beacon_chain/src/events.rs | 14 ++------------ beacon_node/client/src/builder.rs | 1 - beacon_node/http_api/src/lib.rs | 18 ++++++++++++------ beacon_node/src/config.rs | 3 +++ lighthouse/tests/beacon_node.rs | 10 ++++++++-- 5 files changed, 25 insertions(+), 21 deletions(-) diff --git a/beacon_node/beacon_chain/src/events.rs b/beacon_node/beacon_chain/src/events.rs index d52e763a410..0e5dfc80596 100644 --- a/beacon_node/beacon_chain/src/events.rs +++ b/beacon_node/beacon_chain/src/events.rs @@ -117,21 +117,11 @@ impl ServerSentEventHandler { EventKind::LightClientFinalityUpdate(_) => self .light_client_finality_update_tx .send(kind) - .map(|count| { - log_count( - "Registering server-sent light client finality update event", - count, - ) - }), + .map(|count| log_count("light client finality update", count)), EventKind::LightClientOptimisticUpdate(_) => self .light_client_optimistic_update_tx .send(kind) - .map(|count| { - log_count( - "Registering server-sent light client optimistic update event", - count, - ) - }), + .map(|count| log_count("light client optimistic update", count)), EventKind::BlockReward(_) => self .block_reward_tx .send(kind) diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index ce1ab0005d6..cedf347b9a8 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -165,7 +165,6 @@ where } else { None }; - self.http_api_config.enable_light_client_server = config.network.enable_light_client_server; let execution_layer = if let Some(config) = config.execution_layer.clone() { let context = runtime_context.service_context("exec".into()); diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 11984bb88d0..5f2c641af97 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -2397,7 +2397,7 @@ pub fn serve( ); /* - * beacon/rewards + * beacon/light_client */ let beacon_light_client_path = eth_v1 @@ -2405,7 +2405,7 @@ pub fn serve( .and(warp::path("light_client")) .and(chain_filter.clone()); - // GET beacon/light_client/bootrap/{block_root} + // GET beacon/light_client/bootstrap/{block_root} let get_beacon_light_client_bootstrap = beacon_light_client_path .clone() .and(task_spawner_filter.clone()) @@ -2496,8 +2496,11 @@ pub fn serve( e )) }), - _ => Ok(warp::reply::json(&api_types::GenericResponse::from(update)) - .into_response()), + _ => Ok(warp::reply::json(&ForkVersionedResponse { + version: Some(fork_name), + data: update, + }) + .into_response()), } .map(|resp| add_consensus_version_header(resp, fork_name)) }) @@ -2540,8 +2543,11 @@ pub fn serve( e )) }), - _ => Ok(warp::reply::json(&api_types::GenericResponse::from(update)) - .into_response()), + _ => Ok(warp::reply::json(&ForkVersionedResponse { + version: Some(fork_name), + data: update, + }) + .into_response()), } .map(|resp| add_consensus_version_header(resp, fork_name)) }) diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 609626ae88a..9ceab47f336 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -151,6 +151,9 @@ pub fn get_config( client_config.http_api.duplicate_block_status_code = parse_required(cli_args, "http-duplicate-block-status")?; + + client_config.http_api.enable_light_client_server = + cli_args.is_present("light-client-server"); } if let Some(cache_size) = clap_utils::parse_optional(cli_args, "shuffling-cache-size")? { diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index c8e064e224d..eb1341f9181 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -2346,7 +2346,10 @@ fn sync_eth1_chain_disable_deposit_contract_sync_flag() { fn light_client_server_default() { CommandLineTest::new() .run_with_zero_port() - .with_config(|config| assert_eq!(config.network.enable_light_client_server, false)); + .with_config(|config| { + assert_eq!(config.network.enable_light_client_server, false); + assert_eq!(config.http_api.enable_light_client_server, false); + }); } #[test] @@ -2354,7 +2357,10 @@ fn light_client_server_enabled() { CommandLineTest::new() .flag("light-client-server", None) .run_with_zero_port() - .with_config(|config| assert_eq!(config.network.enable_light_client_server, true)); + .with_config(|config| { + assert_eq!(config.network.enable_light_client_server, true); + assert_eq!(config.http_api.enable_light_client_server, true); + }); } #[test] From 75bd2ace39e4b944c8b2ce09bdc061e3ff05ceb9 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Sat, 18 Nov 2023 03:21:44 +1100 Subject: [PATCH 19/72] Add light client bootstrap API test and fix existing ones. --- beacon_node/beacon_chain/src/beacon_chain.rs | 11 +++-- beacon_node/beacon_chain/src/errors.rs | 1 + beacon_node/http_api/src/test_utils.rs | 1 + beacon_node/http_api/tests/tests.rs | 48 +++++++++++++++++-- consensus/types/src/light_client_bootstrap.rs | 3 +- 5 files changed, 55 insertions(+), 9 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index c08a1e227fb..dce2194a08d 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -6469,9 +6469,14 @@ impl BeaconChain { .fork_name(&self.spec) .map_err(Error::InconsistentFork)?; - LightClientBootstrap::from_beacon_state(&mut state) - .map(|bootstrap| Some((bootstrap, fork_name))) - .map_err(Error::LightClientError) + match fork_name { + ForkName::Altair | ForkName::Merge => { + LightClientBootstrap::from_beacon_state(&mut state) + .map(|bootstrap| Some((bootstrap, fork_name))) + .map_err(Error::LightClientError) + } + ForkName::Base | ForkName::Capella | ForkName::Deneb => Err(Error::UnsupportedFork), + } } } diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 8e6018c72b0..9c1ba06f853 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -222,6 +222,7 @@ pub enum BeaconChainError { UnableToPublish, AvailabilityCheckError(AvailabilityCheckError), LightClientError(LightClientError), + UnsupportedFork, } easy_from_to!(SlotProcessingError, BeaconChainError); diff --git a/beacon_node/http_api/src/test_utils.rs b/beacon_node/http_api/src/test_utils.rs index fe47e56dc57..bafb5738194 100644 --- a/beacon_node/http_api/src/test_utils.rs +++ b/beacon_node/http_api/src/test_utils.rs @@ -209,6 +209,7 @@ pub async fn create_api_server( enabled: true, listen_port: port, data_dir: std::path::PathBuf::from(DEFAULT_ROOT_DIR), + enable_light_client_server: true, ..Config::default() }, chain: Some(chain), diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 8e049c5e387..eaee6e6e352 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -1644,6 +1644,26 @@ impl ApiTester { self } + pub async fn test_get_beacon_light_client_bootstrap(self) -> Self { + let block_id = BlockId(CoreBlockId::Finalized); + let (block_root, _, _) = block_id.root(&self.chain).unwrap(); + let (block, _, _) = block_id.full_block(&self.chain).await.unwrap(); + + let result = match self + .client + .get_light_client_bootstrap::(block_root) + .await + { + Ok(result) => result.unwrap().data, + Err(e) => panic!("query failed incorrectly: {e:?}"), + }; + + let expected = block.slot(); + assert_eq!(result.header.beacon.slot, expected); + + self + } + pub async fn test_get_beacon_light_client_optimistic_update(self) -> Self { // get_beacon_light_client_optimistic_update returns Ok(None) on 404 NOT FOUND let result = match self @@ -1652,7 +1672,7 @@ impl ApiTester { .await { Ok(result) => result.map(|res| res.data), - Err(_) => panic!("query did not fail correctly"), + Err(e) => panic!("query failed incorrectly: {e:?}"), }; let expected = self.chain.latest_seen_optimistic_update.lock().clone(); @@ -1668,7 +1688,7 @@ impl ApiTester { .await { Ok(result) => result.map(|res| res.data), - Err(_) => panic!("query did not fail correctly"), + Err(e) => panic!("query failed incorrectly: {e:?}"), }; let expected = self.chain.latest_seen_finality_update.lock().clone(); @@ -5092,9 +5112,25 @@ async fn node_get() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_light_client_bootstrap() { + let config = ApiTesterConfig { + spec: ForkName::Altair.make_genesis_spec(E::default_spec()), + ..<_>::default() + }; + ApiTester::new_from_config(config) + .await + .test_get_beacon_light_client_bootstrap() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_light_client_optimistic_update() { - ApiTester::new() + let config = ApiTesterConfig { + spec: ForkName::Altair.make_genesis_spec(E::default_spec()), + ..<_>::default() + }; + ApiTester::new_from_config(config) .await .test_get_beacon_light_client_optimistic_update() .await; @@ -5102,7 +5138,11 @@ async fn get_light_client_optimistic_update() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_light_client_finality_update() { - ApiTester::new() + let config = ApiTesterConfig { + spec: ForkName::Altair.make_genesis_spec(E::default_spec()), + ..<_>::default() + }; + ApiTester::new_from_config(config) .await .test_get_beacon_light_client_finality_update() .await; diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 14bed794633..616aced483a 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -8,7 +8,6 @@ use serde_json::Value; use ssz_derive::{Decode, Encode}; use std::sync::Arc; use test_random_derive::TestRandom; -use tree_hash::TreeHash; /// A LightClientBootstrap is the initializer we send over to lightclient nodes /// that are trying to generate their basic storage when booting up. @@ -37,7 +36,7 @@ pub struct LightClientBootstrap { impl LightClientBootstrap { pub fn from_beacon_state(beacon_state: &mut BeaconState) -> Result { let mut header = beacon_state.latest_block_header().clone(); - header.state_root = beacon_state.tree_hash_root(); + header.state_root = beacon_state.update_tree_hash_cache()?; let current_sync_committee_branch = beacon_state.compute_merkle_proof(CURRENT_SYNC_COMMITTEE_INDEX)?; Ok(LightClientBootstrap { From e0d0ece7ba2ece330dac7f6a70bebcf1390b4468 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Sat, 18 Nov 2023 03:57:51 +1100 Subject: [PATCH 20/72] Fix test for `light-client-server` http api config. --- lighthouse/tests/beacon_node.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index eb1341f9181..5f75cb1acff 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -2359,6 +2359,16 @@ fn light_client_server_enabled() { .run_with_zero_port() .with_config(|config| { assert_eq!(config.network.enable_light_client_server, true); + }); +} + +#[test] +fn light_client_http_server_enabled() { + CommandLineTest::new() + .flag("http", None) + .flag("light-client-server", None) + .run_with_zero_port() + .with_config(|config| { assert_eq!(config.http_api.enable_light_client_server, true); }); } From 161ece641c777146e3e7aa2fd459ec7c2194bd48 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Sat, 18 Nov 2023 09:28:56 +1100 Subject: [PATCH 21/72] Appease clippy --- beacon_node/beacon_chain/src/beacon_chain.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 46f4fd8cbcd..4a2a308948f 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -6450,6 +6450,7 @@ impl BeaconChain { /// Gets the `LightClientBootstrap` object for a requested block root. /// /// Returns `None` when the state or block is not found in the database. + #[allow(clippy::type_complexity)] pub fn get_light_client_bootstrap( &self, block_root: &Hash256, From 885958e654ff19a1742c531d4217f635141f0fc3 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Tue, 21 Nov 2023 16:24:57 +1100 Subject: [PATCH 22/72] Add Altair light client SSZ tests --- consensus/types/src/light_client_bootstrap.rs | 2 + .../types/src/light_client_finality_update.rs | 2 + consensus/types/src/light_client_header.rs | 2 + .../src/light_client_optimistic_update.rs | 2 + consensus/types/src/light_client_update.rs | 2 + testing/ef_tests/check_all_files_accessed.py | 14 ------- testing/ef_tests/src/type_name.rs | 5 +++ testing/ef_tests/tests/tests.rs | 41 ++++++++++++++++++- 8 files changed, 55 insertions(+), 15 deletions(-) diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 616aced483a..843b3cd514b 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -8,6 +8,7 @@ use serde_json::Value; use ssz_derive::{Decode, Encode}; use std::sync::Arc; use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; /// A LightClientBootstrap is the initializer we send over to lightclient nodes /// that are trying to generate their basic storage when booting up. @@ -19,6 +20,7 @@ use test_random_derive::TestRandom; Deserialize, Encode, Decode, + TreeHash, TestRandom, arbitrary::Arbitrary, )] diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index 87601b81565..b0ae9028896 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -10,6 +10,7 @@ use serde_json::Value; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; /// A LightClientFinalityUpdate is the update lightclient request or received by a gossip that /// signal a new finalized beacon block header for the light client sync protocol. @@ -21,6 +22,7 @@ use tree_hash::TreeHash; Deserialize, Encode, Decode, + TreeHash, TestRandom, arbitrary::Arbitrary, )] diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 8fe31f7af8c..f7d3c633ab9 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -3,6 +3,7 @@ use crate::BeaconBlockHeader; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; #[derive( Debug, @@ -12,6 +13,7 @@ use test_random_derive::TestRandom; Deserialize, Encode, Decode, + TreeHash, TestRandom, arbitrary::Arbitrary, )] diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index d883d735f35..dd17a64342c 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -8,6 +8,7 @@ use serde_json::Value; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; /// A LightClientOptimisticUpdate is the update we send on each slot, /// it is based off the current unfinalized epoch is verified only against BLS signature. @@ -19,6 +20,7 @@ use tree_hash::TreeHash; Deserialize, Encode, Decode, + TreeHash, TestRandom, arbitrary::Arbitrary, )] diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 718cd7553f9..76a3bd3b9bc 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -11,6 +11,7 @@ use ssz_types::typenum::{U5, U6}; use std::sync::Arc; use test_random_derive::TestRandom; use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; pub const FINALIZED_ROOT_INDEX: usize = 105; pub const CURRENT_SYNC_COMMITTEE_INDEX: usize = 54; @@ -64,6 +65,7 @@ impl From for Error { Deserialize, Encode, Decode, + TreeHash, TestRandom, arbitrary::Arbitrary, )] diff --git a/testing/ef_tests/check_all_files_accessed.py b/testing/ef_tests/check_all_files_accessed.py index a5ab897c33f..0fbbc5ed786 100755 --- a/testing/ef_tests/check_all_files_accessed.py +++ b/testing/ef_tests/check_all_files_accessed.py @@ -27,20 +27,6 @@ "tests/.*/.*/ssz_static/PowBlock/", # light_client "tests/.*/.*/light_client", - # LightClientStore - "tests/.*/.*/ssz_static/LightClientStore", - # LightClientUpdate - "tests/.*/.*/ssz_static/LightClientUpdate", - # LightClientSnapshot - "tests/.*/.*/ssz_static/LightClientSnapshot", - # LightClientBootstrap - "tests/.*/.*/ssz_static/LightClientBootstrap", - # LightClientOptimistic - "tests/.*/.*/ssz_static/LightClientOptimistic", - # LightClientFinalityUpdate - "tests/.*/.*/ssz_static/LightClientFinalityUpdate", - # LightClientHeader - "tests/.*/.*/ssz_static/LightClientHeader", # One of the EF researchers likes to pack the tarballs on a Mac ".*\.DS_Store.*", # More Mac weirdness. diff --git a/testing/ef_tests/src/type_name.rs b/testing/ef_tests/src/type_name.rs index ef128440301..e57e2b13598 100644 --- a/testing/ef_tests/src/type_name.rs +++ b/testing/ef_tests/src/type_name.rs @@ -73,6 +73,11 @@ type_name!(Fork); type_name!(ForkData); type_name_generic!(HistoricalBatch); type_name_generic!(IndexedAttestation); +type_name_generic!(LightClientBootstrap); +type_name_generic!(LightClientFinalityUpdate); +type_name!(LightClientHeader); +type_name_generic!(LightClientOptimisticUpdate); +type_name_generic!(LightClientUpdate); type_name_generic!(PendingAttestation); type_name!(ProposerSlashing); type_name_generic!(SignedAggregateAndProof); diff --git a/testing/ef_tests/tests/tests.rs b/testing/ef_tests/tests/tests.rs index d2d30b596cc..000a557a5f5 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -236,7 +236,6 @@ mod ssz_static { ssz_static_test!(fork_data, ForkData); ssz_static_test!(historical_batch, HistoricalBatch<_>); ssz_static_test!(indexed_attestation, IndexedAttestation<_>); - // NOTE: LightClient* intentionally omitted ssz_static_test!(pending_attestation, PendingAttestation<_>); ssz_static_test!(proposer_slashing, ProposerSlashing); ssz_static_test!(signed_aggregate_and_proof, SignedAggregateAndProof<_>); @@ -285,6 +284,46 @@ mod ssz_static { .run(); } + #[test] + fn light_client_bootstrap() { + SszStaticHandler::, MinimalEthSpec>::altair_only() + .run(); + SszStaticHandler::, MainnetEthSpec>::altair_only() + .run(); + } + + #[test] + fn light_client_finality_update() { + SszStaticHandler::, MinimalEthSpec>::altair_only( + ) + .run(); + SszStaticHandler::, MainnetEthSpec>::altair_only( + ) + .run(); + } + + #[test] + fn light_client_header() { + SszStaticHandler::::altair_only().run(); + SszStaticHandler::::altair_only().run(); + } + + #[test] + fn light_client_optimistic_update() { + SszStaticHandler::, MinimalEthSpec>::altair_only( + ) + .run(); + SszStaticHandler::, MainnetEthSpec>::altair_only( + ) + .run(); + } + + #[test] + fn light_client_update() { + SszStaticHandler::, MinimalEthSpec>::altair_only().run(); + SszStaticHandler::, MainnetEthSpec>::altair_only().run(); + } + #[test] fn signed_contribution_and_proof() { SszStaticHandler::, MinimalEthSpec>::altair_and_later().run(); From fe8e2e44b1520ea948045efce481c9c192bfaa3c Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Tue, 21 Nov 2023 14:36:20 -0800 Subject: [PATCH 23/72] updates to light client header --- consensus/types/src/light_client_bootstrap.rs | 2 +- .../types/src/light_client_finality_update.rs | 4 +- consensus/types/src/light_client_header.rs | 87 ++++++++++++++++++- .../src/light_client_optimistic_update.rs | 2 +- consensus/types/src/light_client_update.rs | 4 +- 5 files changed, 89 insertions(+), 10 deletions(-) diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 616aced483a..e1fc01737e0 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -26,7 +26,7 @@ use test_random_derive::TestRandom; #[arbitrary(bound = "T: EthSpec")] pub struct LightClientBootstrap { /// The requested beacon block header. - pub header: LightClientHeader, + pub header: LightClientHeader, /// The `SyncCommittee` used in the requested period. pub current_sync_committee: Arc>, /// Merkle proof for sync committee diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index 87601b81565..44cb8edbd13 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -28,9 +28,9 @@ use tree_hash::TreeHash; #[arbitrary(bound = "T: EthSpec")] pub struct LightClientFinalityUpdate { /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: LightClientHeader, + pub attested_header: LightClientHeader, /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). - pub finalized_header: LightClientHeader, + pub finalized_header: LightClientHeader, /// Merkle proof attesting finalized header. pub finality_branch: FixedVector, /// current sync aggreggate diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 8fe31f7af8c..ba9b71577d8 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,8 +1,13 @@ -use crate::test_utils::TestRandom; use crate::BeaconBlockHeader; +use crate::{test_utils::TestRandom, EthSpec, ExecutionPayloadHeader, Hash256}; +use merkle_proof::verify_merkle_proof; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; +use tree_hash::TreeHash; + +const EXECUTION_PAYLOAD_INDEX: u32 = 25; +const FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX: u32 = 4; #[derive( Debug, @@ -15,12 +20,86 @@ use test_random_derive::TestRandom; TestRandom, arbitrary::Arbitrary, )] -pub struct LightClientHeader { +pub struct ExecutionBranch(pub [u8; FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize]); + +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + TestRandom, + arbitrary::Arbitrary, +)] +#[serde(bound = "E: EthSpec")] +#[arbitrary(bound = "T: EthSpec")] +pub struct LightClientHeader { pub beacon: BeaconBlockHeader, + pub execution: Option>, + pub execution_branch: Option, } -impl From for LightClientHeader { +impl From for LightClientHeader { fn from(beacon: BeaconBlockHeader) -> Self { - LightClientHeader { beacon } + LightClientHeader { + beacon, + execution: None, + execution_branch: None, + } + } +} + +impl LightClientHeader { + fn get_lc_execution_root(&self) -> Option { + let epoch = self.beacon.slot.epoch(E::slots_per_epoch()); + + // TODO greater than or equal to CAPELLA + if epoch >= 0 { + if let Some(execution) = self.execution { + return Some(execution.tree_hash_root()); + } + } + + return None; + } + + fn is_valid_light_client_header(&self) -> bool { + let epoch = self.beacon.slot.epoch(E::slots_per_epoch()); + + // TODO LESS THAN CAPELLA + if epoch < 0 { + return self.execution == None && self.execution_branch == None; + } + + let Some(execution_root) = self.get_lc_execution_root() else { + return false + }; + + let Some(execution_branch) = self.execution_branch else { + return false + }; + + return verify_merkle_proof( + execution_root, + &execution_branch.into(), + FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX, + get_subtree_index(EXECUTION_PAYLOAD_INDEX), + self.beacon.body_root, + ); + } +} + +// TODO move to the relevant place +fn get_subtree_index(generalized_index: u32) -> u32 { + return generalized_index % 2 * (log2_int(generalized_index)); +} + +// TODO move to the relevant place +fn log2_int(x: u32) -> u32 { + if x == 0 { + return 0; } + 31 - x.leading_zeros() } diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index d883d735f35..39b3619d05a 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -26,7 +26,7 @@ use tree_hash::TreeHash; #[arbitrary(bound = "T: EthSpec")] pub struct LightClientOptimisticUpdate { /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: LightClientHeader, + pub attested_header: LightClientHeader, /// current sync aggreggate pub sync_aggregate: SyncAggregate, /// Slot of the sync aggregated singature diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 718cd7553f9..7eb563ef666 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -71,13 +71,13 @@ impl From for Error { #[arbitrary(bound = "T: EthSpec")] pub struct LightClientUpdate { /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: LightClientHeader, + pub attested_header: LightClientHeader, /// The `SyncCommittee` used in the next period. pub next_sync_committee: Arc>, /// Merkle proof for next sync committee pub next_sync_committee_branch: FixedVector, /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). - pub finalized_header: LightClientHeader, + pub finalized_header: LightClientHeader, /// Merkle proof attesting finalized header. pub finality_branch: FixedVector, /// current sync aggreggate From ec1400f2de3a86938d05327a549a7c807eeef6ea Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Tue, 21 Nov 2023 18:42:50 -0800 Subject: [PATCH 24/72] light client header from signed beacon block --- consensus/types/src/light_client_header.rs | 37 ++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index ba9b71577d8..bfb937b9615 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,10 +1,11 @@ use crate::BeaconBlockHeader; -use crate::{test_utils::TestRandom, EthSpec, ExecutionPayloadHeader, Hash256}; -use merkle_proof::verify_merkle_proof; +use crate::{test_utils::TestRandom, EthSpec, ExecutionPayloadHeader, SignedBeaconBlock, Hash256}; +use merkle_proof::{verify_merkle_proof, MerkleTree}; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash::TreeHash; +use ssz::Encode; const EXECUTION_PAYLOAD_INDEX: u32 = 25; const FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX: u32 = 4; @@ -51,6 +52,38 @@ impl From for LightClientHeader { } } +impl From> for LightClientHeader { + fn from(block: SignedBeaconBlock) -> Self { + let epoch = block.message().slot().epoch(E::slots_per_epoch()); + + // TODO epoch greater than or equal to capella + if epoch >= 0 { + let payload = block.message().body().execution_payload(); + + // TODO fix unwrap + let header = ExecutionPayloadHeader::::from(payload.unwrap().into()); + + let leaves = block + .message() + .body_capella() + .unwrap() + .as_ssz_bytes() + .iter() + .map(|data| data.tree_hash_root()) + .collect::>(); + let tree = MerkleTree::create(&leaves, EXECUTION_PAYLOAD_INDEX as usize); + + let execution_branch = generate_proof(block.message().body_capella().unwrap().as_ssz_bytes(), EXECUTION_PAYLOAD_INDEX); + }; + + LightClientHeader { + beacon, + execution: None, + execution_branch: None, + } + } +} + impl LightClientHeader { fn get_lc_execution_root(&self) -> Option { let epoch = self.beacon.slot.epoch(E::slots_per_epoch()); From 435da0272ab2948e782e2166e917366ae7b82d48 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Fri, 24 Nov 2023 13:05:49 -0800 Subject: [PATCH 25/72] using options --- consensus/types/src/light_client_header.rs | 42 ++++++++++++---------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index bfb937b9615..e220ab29195 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,28 +1,28 @@ use crate::BeaconBlockHeader; -use crate::{test_utils::TestRandom, EthSpec, ExecutionPayloadHeader, SignedBeaconBlock, Hash256}; +use crate::{test_utils::TestRandom, EthSpec, ExecutionPayloadHeader, Hash256, SignedBeaconBlock}; use merkle_proof::{verify_merkle_proof, MerkleTree}; use serde::{Deserialize, Serialize}; +use ssz::Encode; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash::TreeHash; -use ssz::Encode; const EXECUTION_PAYLOAD_INDEX: u32 = 25; const FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX: u32 = 4; -#[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - TestRandom, - arbitrary::Arbitrary, -)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, arbitrary::Arbitrary)] +#[ssz(struct_behaviour = "transparent")] pub struct ExecutionBranch(pub [u8; FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize]); +impl TestRandom for Option { + fn random_for_test(rng: &mut impl rand::RngCore) -> Self { + Some(ExecutionBranch(<[u8; FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX + as usize] as TestRandom>::random_for_test( + rng + ))) + } +} + #[derive( Debug, Clone, @@ -35,9 +35,10 @@ pub struct ExecutionBranch(pub [u8; FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize] arbitrary::Arbitrary, )] #[serde(bound = "E: EthSpec")] -#[arbitrary(bound = "T: EthSpec")] +#[arbitrary(bound = "E: EthSpec")] pub struct LightClientHeader { pub beacon: BeaconBlockHeader, + #[test_random(default)] pub execution: Option>, pub execution_branch: Option, } @@ -73,7 +74,10 @@ impl From> for LightClientHeader { .collect::>(); let tree = MerkleTree::create(&leaves, EXECUTION_PAYLOAD_INDEX as usize); - let execution_branch = generate_proof(block.message().body_capella().unwrap().as_ssz_bytes(), EXECUTION_PAYLOAD_INDEX); + let execution_branch = generate_proof( + block.message().body_capella().unwrap().as_ssz_bytes(), + EXECUTION_PAYLOAD_INDEX, + ); }; LightClientHeader { @@ -107,18 +111,18 @@ impl LightClientHeader { } let Some(execution_root) = self.get_lc_execution_root() else { - return false + return false; }; let Some(execution_branch) = self.execution_branch else { - return false + return false; }; return verify_merkle_proof( execution_root, &execution_branch.into(), - FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX, - get_subtree_index(EXECUTION_PAYLOAD_INDEX), + FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize, + get_subtree_index(EXECUTION_PAYLOAD_INDEX) as usize, self.beacon.body_root, ); } From 73a581aa22e866211e53c8acc3f8104a3257778a Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Fri, 24 Nov 2023 18:49:12 -0800 Subject: [PATCH 26/72] implement helper functions --- consensus/types/src/light_client_header.rs | 44 +++++++++++++++------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index e220ab29195..a3da3d90052 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,5 +1,5 @@ -use crate::BeaconBlockHeader; use crate::{test_utils::TestRandom, EthSpec, ExecutionPayloadHeader, Hash256, SignedBeaconBlock}; +use crate::{BeaconBlockHeader, ExecutionPayload}; use merkle_proof::{verify_merkle_proof, MerkleTree}; use serde::{Deserialize, Serialize}; use ssz::Encode; @@ -36,10 +36,13 @@ impl TestRandom for Option { )] #[serde(bound = "E: EthSpec")] #[arbitrary(bound = "E: EthSpec")] +#[ssz(struct_behaviour = "container")] pub struct LightClientHeader { pub beacon: BeaconBlockHeader, #[test_random(default)] + #[ssz(skip_serializing, skip_deserializing)] pub execution: Option>, + #[test_random(default)] pub execution_branch: Option, } @@ -59,11 +62,17 @@ impl From> for LightClientHeader { // TODO epoch greater than or equal to capella if epoch >= 0 { - let payload = block.message().body().execution_payload(); + let payload: ExecutionPayload = block + .message() + .execution_payload() + .unwrap() + .execution_payload_capella() + .unwrap() + .to_owned() + .into(); // TODO fix unwrap - let header = ExecutionPayloadHeader::::from(payload.unwrap().into()); - + let header = ExecutionPayloadHeader::from(payload.to_ref()); let leaves = block .message() .body_capella() @@ -72,16 +81,25 @@ impl From> for LightClientHeader { .iter() .map(|data| data.tree_hash_root()) .collect::>(); - let tree = MerkleTree::create(&leaves, EXECUTION_PAYLOAD_INDEX as usize); - let execution_branch = generate_proof( - block.message().body_capella().unwrap().as_ssz_bytes(), - EXECUTION_PAYLOAD_INDEX, - ); + let tree = MerkleTree::create(&leaves, FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize); + + let _ = tree + .generate_proof( + EXECUTION_PAYLOAD_INDEX as usize, + FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize, + ) + .unwrap(); + + return LightClientHeader { + beacon: block.message().block_header(), + execution: Some(header), + execution_branch: None, // Some(execution_branch), + }; }; LightClientHeader { - beacon, + beacon: block.message().block_header(), execution: None, execution_branch: None, } @@ -94,7 +112,7 @@ impl LightClientHeader { // TODO greater than or equal to CAPELLA if epoch >= 0 { - if let Some(execution) = self.execution { + if let Some(execution) = &self.execution { return Some(execution.tree_hash_root()); } } @@ -114,13 +132,13 @@ impl LightClientHeader { return false; }; - let Some(execution_branch) = self.execution_branch else { + let Some(execution_branch) = &self.execution_branch else { return false; }; return verify_merkle_proof( execution_root, - &execution_branch.into(), + &[Hash256::from_slice(&execution_branch.0)], FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize, get_subtree_index(EXECUTION_PAYLOAD_INDEX) as usize, self.beacon.body_root, From 5ff6f432d6f212dc661dfa3d38037fe0c4c2396f Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Fri, 24 Nov 2023 19:49:04 -0800 Subject: [PATCH 27/72] placeholder conversion from vec hash256 to exec branch --- consensus/types/src/light_client_header.rs | 23 ++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index a3da3d90052..bb85f55837d 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,5 +1,6 @@ use crate::{test_utils::TestRandom, EthSpec, ExecutionPayloadHeader, Hash256, SignedBeaconBlock}; use crate::{BeaconBlockHeader, ExecutionPayload}; +use ethereum_hashing::hash_fixed; use merkle_proof::{verify_merkle_proof, MerkleTree}; use serde::{Deserialize, Serialize}; use ssz::Encode; @@ -23,6 +24,19 @@ impl TestRandom for Option { } } +impl From> for ExecutionBranch { + fn from(proof: Vec) -> Self { + let mut result = [0u8; 4]; + + for (i, h256) in proof.iter().enumerate().take(4) { + result[i] = h256.as_bytes()[0]; + } + + ExecutionBranch(result) + } + +} + #[derive( Debug, Clone, @@ -36,7 +50,6 @@ impl TestRandom for Option { )] #[serde(bound = "E: EthSpec")] #[arbitrary(bound = "E: EthSpec")] -#[ssz(struct_behaviour = "container")] pub struct LightClientHeader { pub beacon: BeaconBlockHeader, #[test_random(default)] @@ -84,7 +97,7 @@ impl From> for LightClientHeader { let tree = MerkleTree::create(&leaves, FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize); - let _ = tree + let (_, proof) = tree .generate_proof( EXECUTION_PAYLOAD_INDEX as usize, FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize, @@ -94,7 +107,7 @@ impl From> for LightClientHeader { return LightClientHeader { beacon: block.message().block_header(), execution: Some(header), - execution_branch: None, // Some(execution_branch), + execution_branch: Some(proof.into()), }; }; @@ -138,7 +151,9 @@ impl LightClientHeader { return verify_merkle_proof( execution_root, - &[Hash256::from_slice(&execution_branch.0)], + &[Hash256::from_slice( + hash_fixed(&execution_branch.0).as_slice(), + )], FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize, get_subtree_index(EXECUTION_PAYLOAD_INDEX) as usize, self.beacon.body_root, From 7aacc654f0004028f6fda3885c67d30da36e46f6 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Fri, 24 Nov 2023 21:21:18 -0800 Subject: [PATCH 28/72] add deneb --- consensus/types/src/light_client_header.rs | 80 ++++++++++++++-------- 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index bb85f55837d..180e81ecb49 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -29,12 +29,11 @@ impl From> for ExecutionBranch { let mut result = [0u8; 4]; for (i, h256) in proof.iter().enumerate().take(4) { - result[i] = h256.as_bytes()[0]; + result[i] = h256.as_bytes()[0]; } ExecutionBranch(result) } - } #[derive( @@ -74,47 +73,56 @@ impl From> for LightClientHeader { let epoch = block.message().slot().epoch(E::slots_per_epoch()); // TODO epoch greater than or equal to capella - if epoch >= 0 { - let payload: ExecutionPayload = block + let payload: ExecutionPayload = if epoch >= 0 { + block .message() .execution_payload() .unwrap() .execution_payload_capella() .unwrap() .to_owned() - .into(); - - // TODO fix unwrap - let header = ExecutionPayloadHeader::from(payload.to_ref()); - let leaves = block + .into() + } else if epoch >= 1 { + block .message() - .body_capella() + .execution_payload() .unwrap() - .as_ssz_bytes() - .iter() - .map(|data| data.tree_hash_root()) - .collect::>(); - - let tree = MerkleTree::create(&leaves, FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize); - - let (_, proof) = tree - .generate_proof( - EXECUTION_PAYLOAD_INDEX as usize, - FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize, - ) - .unwrap(); - + .execution_payload_deneb() + .unwrap() + .to_owned() + .into() + } else { return LightClientHeader { beacon: block.message().block_header(), - execution: Some(header), - execution_branch: Some(proof.into()), + execution: None, + execution_branch: None, }; }; + // TODO fix unwrap + let header = ExecutionPayloadHeader::from(payload.to_ref()); + let leaves = block + .message() + .body_capella() + .unwrap() + .as_ssz_bytes() + .iter() + .map(|data| data.tree_hash_root()) + .collect::>(); + + let tree = MerkleTree::create(&leaves, FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize); + + let (_, proof) = tree + .generate_proof( + EXECUTION_PAYLOAD_INDEX as usize, + FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize, + ) + .unwrap(); + LightClientHeader { beacon: block.message().block_header(), - execution: None, - execution_branch: None, + execution: Some(header), + execution_branch: Some(proof.into()), } } } @@ -136,7 +144,21 @@ impl LightClientHeader { fn is_valid_light_client_header(&self) -> bool { let epoch = self.beacon.slot.epoch(E::slots_per_epoch()); - // TODO LESS THAN CAPELLA + // TODO LESS THAN DENEB + if epoch < 1 { + let Some(execution) = &self.execution else { + return false; + }; + + // TODO unwrap + if execution.blob_gas_used().unwrap().to_owned() != 0 as u64 + || execution.excess_blob_gas().unwrap().to_owned() != 0 as u64 + { + return false; + } + } + + // TODO LESS THAN DENEB if epoch < 0 { return self.execution == None && self.execution_branch == None; } From 3593f523ae5b337befeceb3c485d02c4a93388e5 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Sat, 25 Nov 2023 09:26:52 -0800 Subject: [PATCH 29/72] using fixed vector --- consensus/types/src/light_client_header.rs | 48 ++++------------------ consensus/types/src/light_client_update.rs | 6 ++- 2 files changed, 14 insertions(+), 40 deletions(-) diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 180e81ecb49..515a5111b05 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,40 +1,12 @@ -use crate::{test_utils::TestRandom, EthSpec, ExecutionPayloadHeader, Hash256, SignedBeaconBlock}; +use crate::{test_utils::TestRandom, EthSpec, FixedVector, ExecutionPayloadHeader, Hash256, SignedBeaconBlock}; use crate::{BeaconBlockHeader, ExecutionPayload}; -use ethereum_hashing::hash_fixed; use merkle_proof::{verify_merkle_proof, MerkleTree}; use serde::{Deserialize, Serialize}; use ssz::Encode; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash::TreeHash; - -const EXECUTION_PAYLOAD_INDEX: u32 = 25; -const FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX: u32 = 4; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, arbitrary::Arbitrary)] -#[ssz(struct_behaviour = "transparent")] -pub struct ExecutionBranch(pub [u8; FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize]); - -impl TestRandom for Option { - fn random_for_test(rng: &mut impl rand::RngCore) -> Self { - Some(ExecutionBranch(<[u8; FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX - as usize] as TestRandom>::random_for_test( - rng - ))) - } -} - -impl From> for ExecutionBranch { - fn from(proof: Vec) -> Self { - let mut result = [0u8; 4]; - - for (i, h256) in proof.iter().enumerate().take(4) { - result[i] = h256.as_bytes()[0]; - } - - ExecutionBranch(result) - } -} +use crate::light_client_update::*; #[derive( Debug, @@ -55,7 +27,7 @@ pub struct LightClientHeader { #[ssz(skip_serializing, skip_deserializing)] pub execution: Option>, #[test_random(default)] - pub execution_branch: Option, + pub execution_branch: Option>, } impl From for LightClientHeader { @@ -110,12 +82,12 @@ impl From> for LightClientHeader { .map(|data| data.tree_hash_root()) .collect::>(); - let tree = MerkleTree::create(&leaves, FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize); + let tree = MerkleTree::create(&leaves, EXECUTION_PAYLOAD_PROOF_LEN); let (_, proof) = tree .generate_proof( - EXECUTION_PAYLOAD_INDEX as usize, - FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize, + EXECUTION_PAYLOAD_INDEX, + EXECUTION_PAYLOAD_PROOF_LEN, ) .unwrap(); @@ -173,11 +145,9 @@ impl LightClientHeader { return verify_merkle_proof( execution_root, - &[Hash256::from_slice( - hash_fixed(&execution_branch.0).as_slice(), - )], - FLOOR_LOG2_EXECUTION_PAYLOAD_INDEX as usize, - get_subtree_index(EXECUTION_PAYLOAD_INDEX) as usize, + &execution_branch, + EXECUTION_PAYLOAD_PROOF_LEN, + get_subtree_index(EXECUTION_PAYLOAD_INDEX as u32) as usize, self.beacon.body_root, ); } diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 7eb563ef666..9204105f232 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -7,7 +7,7 @@ use safe_arith::ArithError; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use ssz_derive::{Decode, Encode}; -use ssz_types::typenum::{U5, U6}; +use ssz_types::typenum::{U4, U5, U6}; use std::sync::Arc; use test_random_derive::TestRandom; use tree_hash::TreeHash; @@ -15,14 +15,18 @@ use tree_hash::TreeHash; pub const FINALIZED_ROOT_INDEX: usize = 105; pub const CURRENT_SYNC_COMMITTEE_INDEX: usize = 54; pub const NEXT_SYNC_COMMITTEE_INDEX: usize = 55; +pub const EXECUTION_PAYLOAD_INDEX: usize = 25; pub type FinalizedRootProofLen = U6; pub type CurrentSyncCommitteeProofLen = U5; +pub type ExecutionPayloadProofLen = U4; + pub type NextSyncCommitteeProofLen = U5; pub const FINALIZED_ROOT_PROOF_LEN: usize = 6; pub const CURRENT_SYNC_COMMITTEE_PROOF_LEN: usize = 5; pub const NEXT_SYNC_COMMITTEE_PROOF_LEN: usize = 5; +pub const EXECUTION_PAYLOAD_PROOF_LEN: usize = 4; #[derive(Debug, PartialEq, Clone)] pub enum Error { From 65a5770c3345026955625e1431c690bb4ba5b2fe Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Sun, 26 Nov 2023 09:35:08 -0800 Subject: [PATCH 30/72] remove unwraps --- consensus/types/src/light_client_header.rs | 66 ++++++++++------------ 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 515a5111b05..4c012d30a73 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,4 +1,8 @@ -use crate::{test_utils::TestRandom, EthSpec, FixedVector, ExecutionPayloadHeader, Hash256, SignedBeaconBlock}; +use crate::light_client_update::*; +use crate::{ + test_utils::TestRandom, EthSpec, ExecutionPayloadHeader, FixedVector, Hash256, + SignedBeaconBlock, +}; use crate::{BeaconBlockHeader, ExecutionPayload}; use merkle_proof::{verify_merkle_proof, MerkleTree}; use serde::{Deserialize, Serialize}; @@ -6,7 +10,6 @@ use ssz::Encode; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash::TreeHash; -use crate::light_client_update::*; #[derive( Debug, @@ -40,43 +43,38 @@ impl From for LightClientHeader { } } -impl From> for LightClientHeader { - fn from(block: SignedBeaconBlock) -> Self { +impl LightClientHeader { + fn new(block: SignedBeaconBlock) -> Result { let epoch = block.message().slot().epoch(E::slots_per_epoch()); // TODO epoch greater than or equal to capella let payload: ExecutionPayload = if epoch >= 0 { block .message() - .execution_payload() - .unwrap() - .execution_payload_capella() - .unwrap() + .execution_payload()? + .execution_payload_capella()? .to_owned() .into() } else if epoch >= 1 { block .message() - .execution_payload() - .unwrap() - .execution_payload_deneb() - .unwrap() + .execution_payload()? + .execution_payload_deneb()? .to_owned() .into() } else { - return LightClientHeader { + return Ok(LightClientHeader { beacon: block.message().block_header(), execution: None, execution_branch: None, - }; + }); }; // TODO fix unwrap let header = ExecutionPayloadHeader::from(payload.to_ref()); let leaves = block .message() - .body_capella() - .unwrap() + .body_capella()? .as_ssz_bytes() .iter() .map(|data| data.tree_hash_root()) @@ -84,22 +82,16 @@ impl From> for LightClientHeader { let tree = MerkleTree::create(&leaves, EXECUTION_PAYLOAD_PROOF_LEN); - let (_, proof) = tree - .generate_proof( - EXECUTION_PAYLOAD_INDEX, - EXECUTION_PAYLOAD_PROOF_LEN, - ) - .unwrap(); + let (_, proof) = + tree.generate_proof(EXECUTION_PAYLOAD_INDEX, EXECUTION_PAYLOAD_PROOF_LEN)?; - LightClientHeader { + Ok(LightClientHeader { beacon: block.message().block_header(), execution: Some(header), execution_branch: Some(proof.into()), - } + }) } -} -impl LightClientHeader { fn get_lc_execution_root(&self) -> Option { let epoch = self.beacon.slot.epoch(E::slots_per_epoch()); @@ -110,46 +102,46 @@ impl LightClientHeader { } } - return None; + None } - fn is_valid_light_client_header(&self) -> bool { + fn is_valid_light_client_header(&self) -> Result { let epoch = self.beacon.slot.epoch(E::slots_per_epoch()); // TODO LESS THAN DENEB if epoch < 1 { let Some(execution) = &self.execution else { - return false; + return Ok(false); }; // TODO unwrap - if execution.blob_gas_used().unwrap().to_owned() != 0 as u64 - || execution.excess_blob_gas().unwrap().to_owned() != 0 as u64 + if execution.blob_gas_used()?.to_owned() != 0 as u64 + || execution.excess_blob_gas()?.to_owned() != 0 as u64 { - return false; + return Ok(false); } } // TODO LESS THAN DENEB if epoch < 0 { - return self.execution == None && self.execution_branch == None; + return Ok(self.execution == None && self.execution_branch == None); } let Some(execution_root) = self.get_lc_execution_root() else { - return false; + return Ok(false); }; let Some(execution_branch) = &self.execution_branch else { - return false; + return Ok(false); }; - return verify_merkle_proof( + Ok(verify_merkle_proof( execution_root, &execution_branch, EXECUTION_PAYLOAD_PROOF_LEN, get_subtree_index(EXECUTION_PAYLOAD_INDEX as u32) as usize, self.beacon.body_root, - ); + )) } } From fd1a6917f3482c863427b27159d237524205f435 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Mon, 27 Nov 2023 16:20:04 -0800 Subject: [PATCH 31/72] by epoch --- consensus/types/src/light_client_header.rs | 133 +++++++++++---------- 1 file changed, 67 insertions(+), 66 deletions(-) diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 4c012d30a73..39048930053 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,12 +1,11 @@ -use crate::light_client_update::*; +use crate::{light_client_update::*, ChainSpec}; use crate::{ test_utils::TestRandom, EthSpec, ExecutionPayloadHeader, FixedVector, Hash256, SignedBeaconBlock, }; use crate::{BeaconBlockHeader, ExecutionPayload}; -use merkle_proof::{verify_merkle_proof, MerkleTree}; +use merkle_proof::verify_merkle_proof; use serde::{Deserialize, Serialize}; -use ssz::Encode; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash::TreeHash; @@ -44,90 +43,92 @@ impl From for LightClientHeader { } impl LightClientHeader { - fn new(block: SignedBeaconBlock) -> Result { - let epoch = block.message().slot().epoch(E::slots_per_epoch()); - - // TODO epoch greater than or equal to capella - let payload: ExecutionPayload = if epoch >= 0 { - block - .message() - .execution_payload()? - .execution_payload_capella()? - .to_owned() - .into() - } else if epoch >= 1 { - block - .message() - .execution_payload()? - .execution_payload_deneb()? - .to_owned() - .into() - } else { - return Ok(LightClientHeader { - beacon: block.message().block_header(), - execution: None, - execution_branch: None, - }); + fn new(chain_spec: ChainSpec, block: SignedBeaconBlock) -> Result { + let current_epoch = block.slot().epoch(E::slots_per_epoch()); + + if let Some(deneb_fork_epoch) = chain_spec.deneb_fork_epoch { + if current_epoch >= deneb_fork_epoch { + let payload: ExecutionPayload = block + .message() + .execution_payload()? + .execution_payload_deneb()? + .to_owned() + .into(); + let header = ExecutionPayloadHeader::from(payload.to_ref()); + + // TODO calculate execution branch, i.e. the merkle proof of the execution payload header + return Ok(LightClientHeader { + beacon: block.message().block_header(), + execution: Some(header), + execution_branch: None, + }); + } }; - // TODO fix unwrap - let header = ExecutionPayloadHeader::from(payload.to_ref()); - let leaves = block - .message() - .body_capella()? - .as_ssz_bytes() - .iter() - .map(|data| data.tree_hash_root()) - .collect::>(); - - let tree = MerkleTree::create(&leaves, EXECUTION_PAYLOAD_PROOF_LEN); - - let (_, proof) = - tree.generate_proof(EXECUTION_PAYLOAD_INDEX, EXECUTION_PAYLOAD_PROOF_LEN)?; + if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { + if current_epoch >= capella_fork_epoch { + let payload: ExecutionPayload = block + .message() + .execution_payload()? + .execution_payload_capella()? + .to_owned() + .into(); + let header = ExecutionPayloadHeader::from(payload.to_ref()); + + // TODO calculate execution branch, i.e. the merkle proof of the execution payload header + return Ok(LightClientHeader { + beacon: block.message().block_header(), + execution: Some(header), + execution_branch: None, + }); + } + }; Ok(LightClientHeader { beacon: block.message().block_header(), - execution: Some(header), - execution_branch: Some(proof.into()), + execution: None, + execution_branch: None, }) } - fn get_lc_execution_root(&self) -> Option { - let epoch = self.beacon.slot.epoch(E::slots_per_epoch()); + fn get_lc_execution_root(&self, chain_spec: ChainSpec) -> Option { + let current_epoch = self.beacon.slot.epoch(E::slots_per_epoch()); - // TODO greater than or equal to CAPELLA - if epoch >= 0 { - if let Some(execution) = &self.execution { - return Some(execution.tree_hash_root()); + if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { + if current_epoch >= capella_fork_epoch { + if let Some(execution) = &self.execution { + return Some(execution.tree_hash_root()); + } } } None } - fn is_valid_light_client_header(&self) -> Result { - let epoch = self.beacon.slot.epoch(E::slots_per_epoch()); + fn is_valid_light_client_header(&self, chain_spec: ChainSpec) -> Result { + let current_epoch = self.beacon.slot.epoch(E::slots_per_epoch()); - // TODO LESS THAN DENEB - if epoch < 1 { - let Some(execution) = &self.execution else { - return Ok(false); - }; - - // TODO unwrap - if execution.blob_gas_used()?.to_owned() != 0 as u64 - || execution.excess_blob_gas()?.to_owned() != 0 as u64 - { - return Ok(false); + if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { + if current_epoch < capella_fork_epoch { + return Ok(self.execution == None && self.execution_branch == None); } } - // TODO LESS THAN DENEB - if epoch < 0 { - return Ok(self.execution == None && self.execution_branch == None); + if let Some(deneb_fork_epoch) = chain_spec.deneb_fork_epoch { + if current_epoch < deneb_fork_epoch { + let Some(execution) = &self.execution else { + return Ok(false); + }; + + if execution.blob_gas_used()?.to_owned() != 0 as u64 + || execution.excess_blob_gas()?.to_owned() != 0 as u64 + { + return Ok(false); + } + } } - let Some(execution_root) = self.get_lc_execution_root() else { + let Some(execution_root) = self.get_lc_execution_root(chain_spec) else { return Ok(false); }; From 443d2197da91a3e63ea13475568aff3f05b726ff Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Mon, 27 Nov 2023 19:12:07 -0800 Subject: [PATCH 32/72] compute merkle proof --- consensus/types/src/beacon_block_body.rs | 44 +++++++++++++++++++++- consensus/types/src/light_client_header.rs | 26 +++++++++++-- consensus/types/src/light_client_update.rs | 1 + 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index 2f7c6891e4c..cd86cb9ecb5 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -7,6 +7,7 @@ use ssz_types::VariableList; use std::marker::PhantomData; use superstruct::superstruct; use test_random_derive::TestRandom; +use tree_hash::TreeHash; use tree_hash_derive::TreeHash; pub type KzgCommitments = @@ -42,11 +43,12 @@ pub type KzgCommitmentOpts = cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"), partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant") )] -#[derive(Debug, Clone, Serialize, Deserialize, Derivative, arbitrary::Arbitrary)] +#[derive(Debug, Clone, Serialize, Deserialize, Derivative, TreeHash, arbitrary::Arbitrary)] #[derivative(PartialEq, Hash(bound = "T: EthSpec"))] #[serde(untagged)] #[serde(bound = "T: EthSpec, Payload: AbstractExecPayload")] #[arbitrary(bound = "T: EthSpec, Payload: AbstractExecPayload")] +#[tree_hash(enum_behaviour = "transparent")] pub struct BeaconBlockBody = FullPayload> { pub randao_reveal: Signature, pub eth1_data: Eth1Data, @@ -508,6 +510,46 @@ impl From>> } } +impl BeaconBlockBody { + pub fn compute_merkle_proof( + &mut self, + generalized_index: usize, + ) -> Result, Error> { + let mut leaves = vec![ + self.randao_reveal().tree_hash_root(), + self.eth1_data().tree_hash_root(), + self.graffiti().tree_hash_root(), + self.proposer_slashings().tree_hash_root(), + self.attester_slashings().tree_hash_root(), + self.attestations().tree_hash_root(), + self.deposits().tree_hash_root(), + self.voluntary_exits().tree_hash_root(), + ]; + + if let Ok(sync_aggregate) = self.sync_aggregate() { + leaves.push(sync_aggregate.tree_hash_root()) + }; + + if let Ok(execution_payload) = self.execution_payload() { + leaves.push(execution_payload.tree_hash_root()) + }; + + if let Ok(bls_to_execution_changes) = self.bls_to_execution_changes() { + leaves.push(bls_to_execution_changes.tree_hash_root()) + } + + if let Ok(blob_kzg_commitments) = self.blob_kzg_commitments() { + leaves.push(blob_kzg_commitments.tree_hash_root()) + } + + let depth = light_client_update::EXECUTION_PAYLOAD_PROOF_LEN; + let tree = merkle_proof::MerkleTree::create(&leaves, depth); + let (_, proof) = tree.generate_proof(generalized_index, depth)?; + + Ok(proof) + } +} + /// Util method helpful for logging. pub fn format_kzg_commitments(commitments: &[KzgCommitment]) -> String { let commitment_strings: Vec = commitments.iter().map(|x| x.to_string()).collect(); diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 39048930053..e4abf316372 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,4 +1,4 @@ -use crate::{light_client_update::*, ChainSpec}; +use crate::{light_client_update::*, BeaconBlockBody, ChainSpec}; use crate::{ test_utils::TestRandom, EthSpec, ExecutionPayloadHeader, FixedVector, Hash256, SignedBeaconBlock, @@ -54,13 +54,23 @@ impl LightClientHeader { .execution_payload_deneb()? .to_owned() .into(); + let header = ExecutionPayloadHeader::from(payload.to_ref()); + let mut beacon_block_body = BeaconBlockBody::from( + block + .message() + .body_deneb() + .map_err(|_| Error::BeaconBlockBodyError)? + .to_owned(), + ); + let execution_branch = + beacon_block_body.compute_merkle_proof(EXECUTION_PAYLOAD_INDEX)?; // TODO calculate execution branch, i.e. the merkle proof of the execution payload header return Ok(LightClientHeader { beacon: block.message().block_header(), execution: Some(header), - execution_branch: None, + execution_branch: Some(FixedVector::new(execution_branch)?), }); } }; @@ -73,13 +83,23 @@ impl LightClientHeader { .execution_payload_capella()? .to_owned() .into(); + let header = ExecutionPayloadHeader::from(payload.to_ref()); + let mut beacon_block_body = BeaconBlockBody::from( + block + .message() + .body_capella() + .map_err(|_| Error::BeaconBlockBodyError)? + .to_owned(), + ); + let execution_branch = + beacon_block_body.compute_merkle_proof(EXECUTION_PAYLOAD_INDEX)?; // TODO calculate execution branch, i.e. the merkle proof of the execution payload header return Ok(LightClientHeader { beacon: block.message().block_header(), execution: Some(header), - execution_branch: None, + execution_branch: Some(FixedVector::new(execution_branch)?), }); } }; diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 9204105f232..310f79def50 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -37,6 +37,7 @@ pub enum Error { NotEnoughSyncCommitteeParticipants, MismatchingPeriods, InvalidFinalizedBlock, + BeaconBlockBodyError, } impl From for Error { From a9fa092cb283e44a060957428af83f325b72176f Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Mon, 27 Nov 2023 23:24:47 -0800 Subject: [PATCH 33/72] merkle proof --- consensus/types/src/beacon_block_body.rs | 26 +++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index cd86cb9ecb5..7ea056bf695 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -15,6 +15,14 @@ pub type KzgCommitments = pub type KzgCommitmentOpts = FixedVector, ::MaxBlobsPerBlock>; +/// The number of leaves (including padding) on the `BeaconBlockBody` Merkle tree. +/// +/// ## Note +/// +/// This constant is set with the assumption that there are `> 8` and `<= 16` fields on the +/// `BeaconBlockBody`. **Tree hashing will fail if this value is set incorrectly.** +pub const NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES: usize = 16; + /// The body of a `BeaconChain` block, containing operations. /// /// This *superstruct* abstracts over the hard-fork. @@ -515,6 +523,18 @@ impl BeaconBlockBody { &mut self, generalized_index: usize, ) -> Result, Error> { + + let field_index = match generalized_index { + light_client_update::EXECUTION_PAYLOAD_INDEX => { + // Execution payload is a top-level field, subtract off the generalized indices + // for the internal nodes. Result should be 9. + generalized_index + .checked_sub(NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES) + .ok_or(Error::IndexNotSupported(generalized_index))? + } + _ => return Err(Error::IndexNotSupported(generalized_index)) + }; + let mut leaves = vec![ self.randao_reveal().tree_hash_root(), self.eth1_data().tree_hash_root(), @@ -528,11 +548,11 @@ impl BeaconBlockBody { if let Ok(sync_aggregate) = self.sync_aggregate() { leaves.push(sync_aggregate.tree_hash_root()) - }; + } if let Ok(execution_payload) = self.execution_payload() { leaves.push(execution_payload.tree_hash_root()) - }; + } if let Ok(bls_to_execution_changes) = self.bls_to_execution_changes() { leaves.push(bls_to_execution_changes.tree_hash_root()) @@ -544,7 +564,7 @@ impl BeaconBlockBody { let depth = light_client_update::EXECUTION_PAYLOAD_PROOF_LEN; let tree = merkle_proof::MerkleTree::create(&leaves, depth); - let (_, proof) = tree.generate_proof(generalized_index, depth)?; + let (_, proof) = tree.generate_proof(field_index, depth)?; Ok(proof) } From effeec1e2987e7c36cce0dd46d187e7c57713870 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Mon, 27 Nov 2023 23:32:01 -0800 Subject: [PATCH 34/72] update comments --- consensus/types/src/beacon_block_body.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index 7ea056bf695..f921a0abf1e 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -527,7 +527,9 @@ impl BeaconBlockBody { let field_index = match generalized_index { light_client_update::EXECUTION_PAYLOAD_INDEX => { // Execution payload is a top-level field, subtract off the generalized indices - // for the internal nodes. Result should be 9. + // for the internal nodes. Result should be 9, the field offset of the execution + // payload in the `BeaconBlockBody`: + // https://github.com/ethereum/consensus-specs/blob/dev/specs/deneb/beacon-chain.md#beaconblockbody generalized_index .checked_sub(NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES) .ok_or(Error::IndexNotSupported(generalized_index))? From 03c163f4a7f03294878d849fd8277a4cea4f3854 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Tue, 28 Nov 2023 00:09:04 -0800 Subject: [PATCH 35/72] linting --- consensus/types/src/beacon_block_body.rs | 3 +- consensus/types/src/light_client_header.rs | 32 ++++++++-------------- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index f921a0abf1e..e1d2de704e5 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -523,7 +523,6 @@ impl BeaconBlockBody { &mut self, generalized_index: usize, ) -> Result, Error> { - let field_index = match generalized_index { light_client_update::EXECUTION_PAYLOAD_INDEX => { // Execution payload is a top-level field, subtract off the generalized indices @@ -534,7 +533,7 @@ impl BeaconBlockBody { .checked_sub(NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES) .ok_or(Error::IndexNotSupported(generalized_index))? } - _ => return Err(Error::IndexNotSupported(generalized_index)) + _ => return Err(Error::IndexNotSupported(generalized_index)), }; let mut leaves = vec![ diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index e4abf316372..a0ec14880e0 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,3 +1,4 @@ +use crate::beacon_block_body::NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES; use crate::{light_client_update::*, BeaconBlockBody, ChainSpec}; use crate::{ test_utils::TestRandom, EthSpec, ExecutionPayloadHeader, FixedVector, Hash256, @@ -66,7 +67,6 @@ impl LightClientHeader { let execution_branch = beacon_block_body.compute_merkle_proof(EXECUTION_PAYLOAD_INDEX)?; - // TODO calculate execution branch, i.e. the merkle proof of the execution payload header return Ok(LightClientHeader { beacon: block.message().block_header(), execution: Some(header), @@ -95,7 +95,6 @@ impl LightClientHeader { let execution_branch = beacon_block_body.compute_merkle_proof(EXECUTION_PAYLOAD_INDEX)?; - // TODO calculate execution branch, i.e. the merkle proof of the execution payload header return Ok(LightClientHeader { beacon: block.message().block_header(), execution: Some(header), @@ -130,7 +129,7 @@ impl LightClientHeader { if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { if current_epoch < capella_fork_epoch { - return Ok(self.execution == None && self.execution_branch == None); + return Ok(self.execution.is_none() && self.execution_branch.is_none()); } } @@ -140,9 +139,7 @@ impl LightClientHeader { return Ok(false); }; - if execution.blob_gas_used()?.to_owned() != 0 as u64 - || execution.excess_blob_gas()?.to_owned() != 0 as u64 - { + if *execution.blob_gas_used()? != 0_u64 || *execution.excess_blob_gas()? != 0_u64 { return Ok(false); } } @@ -156,25 +153,18 @@ impl LightClientHeader { return Ok(false); }; + let Some(field_index) = + EXECUTION_PAYLOAD_INDEX.checked_sub(NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES) + else { + return Ok(false); + }; + Ok(verify_merkle_proof( execution_root, - &execution_branch, + execution_branch, EXECUTION_PAYLOAD_PROOF_LEN, - get_subtree_index(EXECUTION_PAYLOAD_INDEX as u32) as usize, + field_index, self.beacon.body_root, )) } } - -// TODO move to the relevant place -fn get_subtree_index(generalized_index: u32) -> u32 { - return generalized_index % 2 * (log2_int(generalized_index)); -} - -// TODO move to the relevant place -fn log2_int(x: u32) -> u32 { - if x == 0 { - return 0; - } - 31 - x.leading_zeros() -} From 16740c27e5c2aa3bcc7dc99c613f8b76be4780d8 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Fri, 1 Dec 2023 09:24:14 -0800 Subject: [PATCH 36/72] superstruct attempt --- consensus/types/src/light_client_header.rs | 63 +++++++++++++++------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index a0ec14880e0..2c5b1a79b05 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,7 +1,7 @@ use crate::beacon_block_body::NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES; use crate::{light_client_update::*, BeaconBlockBody, ChainSpec}; use crate::{ - test_utils::TestRandom, EthSpec, ExecutionPayloadHeader, FixedVector, Hash256, + test_utils::TestRandom, EthSpec, ExecutionPayloadHeader, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, FixedVector, Hash256, SignedBeaconBlock, }; use crate::{BeaconBlockHeader, ExecutionPayload}; @@ -10,35 +10,58 @@ use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash::TreeHash; - +use superstruct::superstruct; +use std::marker::PhantomData; + +#[superstruct( + variants(Merge, Capella, Deneb), + variant_attributes( + derive( + Default, + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + TestRandom, + arbitrary::Arbitrary + ), + serde(bound = "E: EthSpec", deny_unknown_fields), + arbitrary(bound = "E: EthSpec"), + ), +)] #[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - TestRandom, - arbitrary::Arbitrary, + Debug, Clone, Serialize, PartialEq, Deserialize, Encode, Decode, arbitrary::Arbitrary, )] -#[serde(bound = "E: EthSpec")] #[arbitrary(bound = "E: EthSpec")] +#[serde(untagged)] +#[serde(bound = "E: EthSpec")] +#[ssz(enum_behaviour = "union")] pub struct LightClientHeader { pub beacon: BeaconBlockHeader, - #[test_random(default)] + #[superstruct(only(Capella, Deneb))] + pub execution_branch: FixedVector, + #[superstruct( + only(Capella), + partial_getter(rename = "execution_payload_header_capella") + )] + pub execution: ExecutionPayloadHeaderCapella, + #[superstruct( + only(Deneb), + partial_getter(rename = "execution_payload_header_deneb") + )] + pub execution: ExecutionPayloadHeaderDeneb, + #[ssz(skip_serializing, skip_deserializing)] - pub execution: Option>, - #[test_random(default)] - pub execution_branch: Option>, + pub phantom_data: PhantomData, } -impl From for LightClientHeader { +impl From for LightClientHeaderMerge { fn from(beacon: BeaconBlockHeader) -> Self { - LightClientHeader { + LightClientHeaderMerge { beacon, - execution: None, - execution_branch: None, } } } From 302a1cbeefd0a38b2824e5a9689c8dd8db55e0f6 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Sat, 2 Dec 2023 22:51:09 -0800 Subject: [PATCH 37/72] superstruct changes --- consensus/types/src/beacon_block_header.rs | 1 + consensus/types/src/light_client_bootstrap.rs | 31 ++- .../types/src/light_client_finality_update.rs | 48 +--- consensus/types/src/light_client_header.rs | 213 ++++++++---------- .../src/light_client_optimistic_update.rs | 26 +-- consensus/types/src/light_client_update.rs | 42 +++- 6 files changed, 180 insertions(+), 181 deletions(-) diff --git a/consensus/types/src/beacon_block_header.rs b/consensus/types/src/beacon_block_header.rs index 689f1a28b08..f8aa61f3dfb 100644 --- a/consensus/types/src/beacon_block_header.rs +++ b/consensus/types/src/beacon_block_header.rs @@ -13,6 +13,7 @@ use tree_hash_derive::TreeHash; #[derive( arbitrary::Arbitrary, Debug, + Default, PartialEq, Eq, Hash, diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index e1fc01737e0..d28fc6931bc 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -1,7 +1,7 @@ use super::{BeaconState, EthSpec, FixedVector, Hash256, SyncCommittee}; use crate::{ light_client_update::*, test_utils::TestRandom, ForkName, ForkVersionDeserialize, - LightClientHeader, + LightClientHeader, SignedBeaconBlock, ChainSpec, light_client_header::{LightClientHeaderDeneb, LightClientHeaderCapella, LightClientHeaderMerge}, }; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; @@ -34,13 +34,38 @@ pub struct LightClientBootstrap { } impl LightClientBootstrap { - pub fn from_beacon_state(beacon_state: &mut BeaconState) -> Result { + pub fn from_beacon_state( + chain_spec: &ChainSpec, + beacon_state: &mut BeaconState, + block: SignedBeaconBlock, + ) -> Result { let mut header = beacon_state.latest_block_header().clone(); header.state_root = beacon_state.update_tree_hash_cache()?; let current_sync_committee_branch = beacon_state.compute_merkle_proof(CURRENT_SYNC_COMMITTEE_INDEX)?; + + if let Some(deneb_fork_epoch) = chain_spec.deneb_fork_epoch { + if beacon_state.slot().epoch(T::slots_per_epoch()) >= deneb_fork_epoch { + return Ok(LightClientBootstrap { + header: LightClientHeaderDeneb::new(block)?.into(), + current_sync_committee: beacon_state.current_sync_committee()?.clone(), + current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, + }) + } + }; + + if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { + if beacon_state.slot().epoch(T::slots_per_epoch()) >= capella_fork_epoch { + return Ok(LightClientBootstrap { + header: LightClientHeaderCapella::new(block)?.into(), + current_sync_committee: beacon_state.current_sync_committee()?.clone(), + current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, + }) + } + }; + Ok(LightClientBootstrap { - header: header.into(), + header: LightClientHeaderMerge::new(block)?.into(), current_sync_committee: beacon_state.current_sync_committee()?.clone(), current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, }) diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index 44cb8edbd13..5418b4e2483 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -2,8 +2,12 @@ use super::{ EthSpec, FixedVector, Hash256, SignedBeaconBlock, SignedBlindedBeaconBlock, Slot, SyncAggregate, }; use crate::{ - light_client_update::*, test_utils::TestRandom, BeaconState, ChainSpec, ForkName, - ForkVersionDeserialize, LightClientHeader, + light_client_header::{ + LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderMerge, + }, + light_client_update::*, + test_utils::TestRandom, + BeaconState, ChainSpec, ForkName, ForkVersionDeserialize, LightClientHeader, }; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; @@ -21,7 +25,6 @@ use tree_hash::TreeHash; Deserialize, Encode, Decode, - TestRandom, arbitrary::Arbitrary, )] #[serde(bound = "T: EthSpec")] @@ -41,41 +44,14 @@ pub struct LightClientFinalityUpdate { impl LightClientFinalityUpdate { pub fn new( - chain_spec: &ChainSpec, - beacon_state: &BeaconState, - block: &SignedBeaconBlock, - attested_state: &mut BeaconState, - finalized_block: &SignedBlindedBeaconBlock, + update: LightClientUpdate, ) -> Result { - let altair_fork_epoch = chain_spec - .altair_fork_epoch - .ok_or(Error::AltairForkNotActive)?; - if beacon_state.slot().epoch(T::slots_per_epoch()) < altair_fork_epoch { - return Err(Error::AltairForkNotActive); - } - - let sync_aggregate = block.message().body().sync_aggregate()?; - if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { - return Err(Error::NotEnoughSyncCommitteeParticipants); - } - - // Compute and validate attested header. - let mut attested_header = attested_state.latest_block_header().clone(); - attested_header.state_root = attested_state.update_tree_hash_cache()?; - // Build finalized header from finalized block - let finalized_header = finalized_block.message().block_header(); - - if finalized_header.tree_hash_root() != beacon_state.finalized_checkpoint().root { - return Err(Error::InvalidFinalizedBlock); - } - - let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; Ok(Self { - attested_header: attested_header.into(), - finalized_header: finalized_header.into(), - finality_branch: FixedVector::new(finality_branch)?, - sync_aggregate: sync_aggregate.clone(), - signature_slot: block.slot(), + attested_header: update.attested_header, + finalized_header: update.finalized_header, + finality_branch: update.finality_branch, + sync_aggregate: update.sync_aggregate, + signature_slot: update.signature_slot, }) } } diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 2c5b1a79b05..a059cbda1b1 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,17 +1,18 @@ use crate::beacon_block_body::NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES; use crate::{light_client_update::*, BeaconBlockBody, ChainSpec}; use crate::{ - test_utils::TestRandom, EthSpec, ExecutionPayloadHeader, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, FixedVector, Hash256, - SignedBeaconBlock, + test_utils::TestRandom, EthSpec, ExecutionPayloadHeader, ExecutionPayloadHeaderCapella, + ExecutionPayloadHeaderDeneb, FixedVector, Hash256, SignedBeaconBlock, }; use crate::{BeaconBlockHeader, ExecutionPayload}; use merkle_proof::verify_merkle_proof; use serde::{Deserialize, Serialize}; +use ssz::Encode; use ssz_derive::{Decode, Encode}; +use std::marker::PhantomData; +use superstruct::superstruct; use test_random_derive::TestRandom; use tree_hash::TreeHash; -use superstruct::superstruct; -use std::marker::PhantomData; #[superstruct( variants(Merge, Capella, Deneb), @@ -30,11 +31,9 @@ use std::marker::PhantomData; ), serde(bound = "E: EthSpec", deny_unknown_fields), arbitrary(bound = "E: EthSpec"), - ), -)] -#[derive( - Debug, Clone, Serialize, PartialEq, Deserialize, Encode, Decode, arbitrary::Arbitrary, + ) )] +#[derive(Debug, Clone, Serialize, PartialEq, Deserialize, Encode, Decode, arbitrary::Arbitrary)] #[arbitrary(bound = "E: EthSpec")] #[serde(untagged)] #[serde(bound = "E: EthSpec")] @@ -48,131 +47,115 @@ pub struct LightClientHeader { partial_getter(rename = "execution_payload_header_capella") )] pub execution: ExecutionPayloadHeaderCapella, - #[superstruct( - only(Deneb), - partial_getter(rename = "execution_payload_header_deneb") - )] + #[superstruct(only(Deneb), partial_getter(rename = "execution_payload_header_deneb"))] pub execution: ExecutionPayloadHeaderDeneb, - + #[ssz(skip_serializing, skip_deserializing)] pub phantom_data: PhantomData, } -impl From for LightClientHeaderMerge { - fn from(beacon: BeaconBlockHeader) -> Self { - LightClientHeaderMerge { - beacon, - } +impl LightClientHeaderMerge { + pub fn new(block: SignedBeaconBlock) -> Result { + Ok(LightClientHeaderMerge { + beacon: block.message().block_header(), + phantom_data: PhantomData, + }) } -} -impl LightClientHeader { - fn new(chain_spec: ChainSpec, block: SignedBeaconBlock) -> Result { - let current_epoch = block.slot().epoch(E::slots_per_epoch()); - - if let Some(deneb_fork_epoch) = chain_spec.deneb_fork_epoch { - if current_epoch >= deneb_fork_epoch { - let payload: ExecutionPayload = block - .message() - .execution_payload()? - .execution_payload_deneb()? - .to_owned() - .into(); - - let header = ExecutionPayloadHeader::from(payload.to_ref()); - let mut beacon_block_body = BeaconBlockBody::from( - block - .message() - .body_deneb() - .map_err(|_| Error::BeaconBlockBodyError)? - .to_owned(), - ); - let execution_branch = - beacon_block_body.compute_merkle_proof(EXECUTION_PAYLOAD_INDEX)?; - - return Ok(LightClientHeader { - beacon: block.message().block_header(), - execution: Some(header), - execution_branch: Some(FixedVector::new(execution_branch)?), - }); - } - }; + fn get_lc_execution_root(&self) -> Option { + return None; + } - if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { - if current_epoch >= capella_fork_epoch { - let payload: ExecutionPayload = block - .message() - .execution_payload()? - .execution_payload_capella()? - .to_owned() - .into(); - - let header = ExecutionPayloadHeader::from(payload.to_ref()); - let mut beacon_block_body = BeaconBlockBody::from( - block - .message() - .body_capella() - .map_err(|_| Error::BeaconBlockBodyError)? - .to_owned(), - ); - let execution_branch = - beacon_block_body.compute_merkle_proof(EXECUTION_PAYLOAD_INDEX)?; - - return Ok(LightClientHeader { - beacon: block.message().block_header(), - execution: Some(header), - execution_branch: Some(FixedVector::new(execution_branch)?), - }); - } - }; + fn is_valid_light_client_header(&self) -> Result { + return Ok(true); + } +} - Ok(LightClientHeader { +impl LightClientHeaderCapella { + pub fn new(block: SignedBeaconBlock) -> Result { + let payload = block + .message() + .execution_payload()? + .execution_payload_capella()? + .to_owned(); + + let header = ExecutionPayloadHeaderCapella::from(&payload); + let mut beacon_block_body = BeaconBlockBody::from( + block + .message() + .body_capella() + .map_err(|_| Error::BeaconBlockBodyError)? + .to_owned(), + ); + + let execution_branch = beacon_block_body.compute_merkle_proof(EXECUTION_PAYLOAD_INDEX)?; + + return Ok(LightClientHeaderCapella { beacon: block.message().block_header(), - execution: None, - execution_branch: None, - }) + execution: header, + execution_branch: FixedVector::new(execution_branch)?, + phantom_data: PhantomData, + }); } - fn get_lc_execution_root(&self, chain_spec: ChainSpec) -> Option { - let current_epoch = self.beacon.slot.epoch(E::slots_per_epoch()); - - if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { - if current_epoch >= capella_fork_epoch { - if let Some(execution) = &self.execution { - return Some(execution.tree_hash_root()); - } - } - } - - None + fn get_lc_execution_root(&self) -> Option { + return Some(self.execution.tree_hash_root()); } - fn is_valid_light_client_header(&self, chain_spec: ChainSpec) -> Result { - let current_epoch = self.beacon.slot.epoch(E::slots_per_epoch()); + fn is_valid_light_client_header(&self) -> Result { + let Some(execution_root) = self.get_lc_execution_root() else { + return Ok(false); + }; - if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { - if current_epoch < capella_fork_epoch { - return Ok(self.execution.is_none() && self.execution_branch.is_none()); - } - } + let Some(field_index) = + EXECUTION_PAYLOAD_INDEX.checked_sub(NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES) + else { + return Ok(false); + }; - if let Some(deneb_fork_epoch) = chain_spec.deneb_fork_epoch { - if current_epoch < deneb_fork_epoch { - let Some(execution) = &self.execution else { - return Ok(false); - }; + Ok(verify_merkle_proof( + execution_root, + &self.execution_branch, + EXECUTION_PAYLOAD_PROOF_LEN, + field_index, + self.beacon.body_root, + )) + } +} - if *execution.blob_gas_used()? != 0_u64 || *execution.excess_blob_gas()? != 0_u64 { - return Ok(false); - } - } - } +impl LightClientHeaderDeneb { + pub fn new(block: SignedBeaconBlock) -> Result { + let payload = block + .message() + .execution_payload()? + .execution_payload_deneb()? + .to_owned(); + + let header = ExecutionPayloadHeaderDeneb::from(&payload); + let mut beacon_block_body = BeaconBlockBody::from( + block + .message() + .body_deneb() + .map_err(|_| Error::BeaconBlockBodyError)? + .to_owned(), + ); + + let execution_branch = beacon_block_body.compute_merkle_proof(EXECUTION_PAYLOAD_INDEX)?; + + Ok(LightClientHeaderDeneb { + beacon: block.message().block_header(), + execution: header, + execution_branch: FixedVector::new(execution_branch)?, + phantom_data: PhantomData, + }) + } - let Some(execution_root) = self.get_lc_execution_root(chain_spec) else { - return Ok(false); - }; + fn get_lc_execution_root(&self) -> Option { + return Some(self.execution.tree_hash_root()); + } - let Some(execution_branch) = &self.execution_branch else { + fn is_valid_light_client_header(&self) -> Result { + let Some(execution_root) = self.get_lc_execution_root() else { return Ok(false); }; @@ -184,7 +167,7 @@ impl LightClientHeader { Ok(verify_merkle_proof( execution_root, - execution_branch, + &self.execution_branch, EXECUTION_PAYLOAD_PROOF_LEN, field_index, self.beacon.body_root, diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index 39b3619d05a..dc6930f4acb 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -1,4 +1,5 @@ use super::{EthSpec, ForkName, ForkVersionDeserialize, Slot, SyncAggregate}; +use crate::LightClientUpdate; use crate::light_client_header::LightClientHeader; use crate::{ light_client_update::Error, test_utils::TestRandom, BeaconState, ChainSpec, SignedBeaconBlock, @@ -35,29 +36,12 @@ pub struct LightClientOptimisticUpdate { impl LightClientOptimisticUpdate { pub fn new( - chain_spec: &ChainSpec, - block: &SignedBeaconBlock, - attested_state: &BeaconState, + update: LightClientUpdate, ) -> Result { - let altair_fork_epoch = chain_spec - .altair_fork_epoch - .ok_or(Error::AltairForkNotActive)?; - if attested_state.slot().epoch(T::slots_per_epoch()) < altair_fork_epoch { - return Err(Error::AltairForkNotActive); - } - - let sync_aggregate = block.message().body().sync_aggregate()?; - if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { - return Err(Error::NotEnoughSyncCommitteeParticipants); - } - - // Compute and validate attested header. - let mut attested_header = attested_state.latest_block_header().clone(); - attested_header.state_root = attested_state.tree_hash_root(); Ok(Self { - attested_header: attested_header.into(), - sync_aggregate: sync_aggregate.clone(), - signature_slot: block.slot(), + attested_header: update.attested_header, + sync_aggregate: update.sync_aggregate, + signature_slot: update.signature_slot, }) } } diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 310f79def50..bd4704a119d 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, ForkName, - ForkVersionDeserialize, LightClientHeader, + ForkVersionDeserialize, LightClientHeader, SignedBeaconBlock, light_client_header::{LightClientHeaderDeneb, LightClientHeaderCapella, LightClientHeaderMerge}, }; use safe_arith::ArithError; use serde::{Deserialize, Deserializer, Serialize}; @@ -97,7 +97,8 @@ impl LightClientUpdate { beacon_state: BeaconState, block: BeaconBlock, attested_state: &mut BeaconState, - finalized_block: BeaconBlock, + attested_block: SignedBeaconBlock, + finalized_block: SignedBeaconBlock, ) -> Result { let altair_fork_epoch = chain_spec .altair_fork_epoch @@ -125,10 +126,10 @@ impl LightClientUpdate { // Build finalized header from finalized block let finalized_header = BeaconBlockHeader { slot: finalized_block.slot(), - proposer_index: finalized_block.proposer_index(), + proposer_index: finalized_block.message().proposer_index(), parent_root: finalized_block.parent_root(), state_root: finalized_block.state_root(), - body_root: finalized_block.body_root(), + body_root: finalized_block.message().body_root(), }; if finalized_header.tree_hash_root() != beacon_state.finalized_checkpoint().root { return Err(Error::InvalidFinalizedBlock); @@ -136,11 +137,40 @@ impl LightClientUpdate { let next_sync_committee_branch = attested_state.compute_merkle_proof(NEXT_SYNC_COMMITTEE_INDEX)?; let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; + + if let Some(deneb_fork_epoch) = chain_spec.deneb_fork_epoch { + if beacon_state.slot().epoch(T::slots_per_epoch()) >= deneb_fork_epoch { + return Ok(Self { + attested_header: LightClientHeaderDeneb::new(attested_block)?.into(), + next_sync_committee: attested_state.next_sync_committee()?.clone(), + next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?, + finalized_header: LightClientHeaderDeneb::new(finalized_block)?.into(), + finality_branch: FixedVector::new(finality_branch)?, + sync_aggregate: sync_aggregate.clone(), + signature_slot: block.slot(), + }) + } + }; + + if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { + if beacon_state.slot().epoch(T::slots_per_epoch()) >= capella_fork_epoch { + return Ok(Self { + attested_header: LightClientHeaderCapella::new(attested_block)?.into(), + next_sync_committee: attested_state.next_sync_committee()?.clone(), + next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?, + finalized_header: LightClientHeaderCapella::new(finalized_block)?.into(), + finality_branch: FixedVector::new(finality_branch)?, + sync_aggregate: sync_aggregate.clone(), + signature_slot: block.slot(), + }) + } + }; + Ok(Self { - attested_header: attested_header.into(), + attested_header: LightClientHeaderMerge::new(attested_block)?.into(), next_sync_committee: attested_state.next_sync_committee()?.clone(), next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?, - finalized_header: finalized_header.into(), + finalized_header: LightClientHeaderMerge::new(finalized_block)?.into(), finality_branch: FixedVector::new(finality_branch)?, sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), From 79eeefdd3d9d15bf42ca88b3946629e43a3ef5fe Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Sun, 3 Dec 2023 10:59:37 -0800 Subject: [PATCH 38/72] lint --- .../lighthouse_network/src/rpc/methods.rs | 2 +- consensus/types/src/light_client_bootstrap.rs | 28 +++++++---------- .../types/src/light_client_finality_update.rs | 30 +++---------------- consensus/types/src/light_client_header.rs | 23 ++++++++------ .../src/light_client_optimistic_update.rs | 24 +++------------ consensus/types/src/light_client_update.rs | 29 +++++++----------- 6 files changed, 44 insertions(+), 92 deletions(-) diff --git a/beacon_node/lighthouse_network/src/rpc/methods.rs b/beacon_node/lighthouse_network/src/rpc/methods.rs index 627c871c471..e468ec8531d 100644 --- a/beacon_node/lighthouse_network/src/rpc/methods.rs +++ b/beacon_node/lighthouse_network/src/rpc/methods.rs @@ -574,7 +574,7 @@ impl std::fmt::Display for RPCResponse { write!( f, "LightClientBootstrap Slot: {}", - bootstrap.header.beacon.slot + bootstrap.header.beacon().slot ) } } diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index d28fc6931bc..5a69f18ce99 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -1,27 +1,19 @@ use super::{BeaconState, EthSpec, FixedVector, Hash256, SyncCommittee}; use crate::{ - light_client_update::*, test_utils::TestRandom, ForkName, ForkVersionDeserialize, - LightClientHeader, SignedBeaconBlock, ChainSpec, light_client_header::{LightClientHeaderDeneb, LightClientHeaderCapella, LightClientHeaderMerge}, + light_client_header::{ + LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderMerge, + }, + light_client_update::*, + ChainSpec, ForkName, ForkVersionDeserialize, LightClientHeader, SignedBeaconBlock, }; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use ssz_derive::{Decode, Encode}; use std::sync::Arc; -use test_random_derive::TestRandom; /// A LightClientBootstrap is the initializer we send over to lightclient nodes /// that are trying to generate their basic storage when booting up. -#[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - TestRandom, - arbitrary::Arbitrary, -)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, arbitrary::Arbitrary)] #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] pub struct LightClientBootstrap { @@ -45,22 +37,22 @@ impl LightClientBootstrap { beacon_state.compute_merkle_proof(CURRENT_SYNC_COMMITTEE_INDEX)?; if let Some(deneb_fork_epoch) = chain_spec.deneb_fork_epoch { - if beacon_state.slot().epoch(T::slots_per_epoch()) >= deneb_fork_epoch { + if beacon_state.slot().epoch(T::slots_per_epoch()) >= deneb_fork_epoch { return Ok(LightClientBootstrap { header: LightClientHeaderDeneb::new(block)?.into(), current_sync_committee: beacon_state.current_sync_committee()?.clone(), current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, - }) + }); } }; if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { - if beacon_state.slot().epoch(T::slots_per_epoch()) >= capella_fork_epoch { + if beacon_state.slot().epoch(T::slots_per_epoch()) >= capella_fork_epoch { return Ok(LightClientBootstrap { header: LightClientHeaderCapella::new(block)?.into(), current_sync_committee: beacon_state.current_sync_committee()?.clone(), current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, - }) + }); } }; diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index 5418b4e2483..aebf9e84a04 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -1,32 +1,12 @@ -use super::{ - EthSpec, FixedVector, Hash256, SignedBeaconBlock, SignedBlindedBeaconBlock, Slot, SyncAggregate, -}; -use crate::{ - light_client_header::{ - LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderMerge, - }, - light_client_update::*, - test_utils::TestRandom, - BeaconState, ChainSpec, ForkName, ForkVersionDeserialize, LightClientHeader, -}; +use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate}; +use crate::{light_client_update::*, ForkName, ForkVersionDeserialize, LightClientHeader}; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use ssz_derive::{Decode, Encode}; -use test_random_derive::TestRandom; -use tree_hash::TreeHash; /// A LightClientFinalityUpdate is the update lightclient request or received by a gossip that /// signal a new finalized beacon block header for the light client sync protocol. -#[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - arbitrary::Arbitrary, -)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, arbitrary::Arbitrary)] #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] pub struct LightClientFinalityUpdate { @@ -43,9 +23,7 @@ pub struct LightClientFinalityUpdate { } impl LightClientFinalityUpdate { - pub fn new( - update: LightClientUpdate, - ) -> Result { + pub fn new(update: LightClientUpdate) -> Result { Ok(Self { attested_header: update.attested_header, finalized_header: update.finalized_header, diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index a059cbda1b1..2cb3067e572 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,13 +1,12 @@ use crate::beacon_block_body::NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES; -use crate::{light_client_update::*, BeaconBlockBody, ChainSpec}; +use crate::BeaconBlockHeader; +use crate::{light_client_update::*, BeaconBlockBody}; use crate::{ - test_utils::TestRandom, EthSpec, ExecutionPayloadHeader, ExecutionPayloadHeaderCapella, - ExecutionPayloadHeaderDeneb, FixedVector, Hash256, SignedBeaconBlock, + test_utils::TestRandom, EthSpec, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, + FixedVector, Hash256, SignedBeaconBlock, }; -use crate::{BeaconBlockHeader, ExecutionPayload}; use merkle_proof::verify_merkle_proof; use serde::{Deserialize, Serialize}; -use ssz::Encode; use ssz_derive::{Decode, Encode}; use std::marker::PhantomData; use superstruct::superstruct; @@ -62,12 +61,14 @@ impl LightClientHeaderMerge { }) } + #[allow(dead_code)] fn get_lc_execution_root(&self) -> Option { - return None; + None } + #[allow(dead_code)] fn is_valid_light_client_header(&self) -> Result { - return Ok(true); + Ok(true) } } @@ -98,10 +99,12 @@ impl LightClientHeaderCapella { }); } + #[allow(dead_code)] fn get_lc_execution_root(&self) -> Option { - return Some(self.execution.tree_hash_root()); + Some(self.execution.tree_hash_root()) } + #[allow(dead_code)] fn is_valid_light_client_header(&self) -> Result { let Some(execution_root) = self.get_lc_execution_root() else { return Ok(false); @@ -150,10 +153,12 @@ impl LightClientHeaderDeneb { }) } + #[allow(dead_code)] fn get_lc_execution_root(&self) -> Option { - return Some(self.execution.tree_hash_root()); + Some(self.execution.tree_hash_root()) } + #[allow(dead_code)] fn is_valid_light_client_header(&self) -> Result { let Some(execution_root) = self.get_lc_execution_root() else { return Ok(false); diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index dc6930f4acb..9150422cf52 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -1,28 +1,14 @@ use super::{EthSpec, ForkName, ForkVersionDeserialize, Slot, SyncAggregate}; -use crate::LightClientUpdate; use crate::light_client_header::LightClientHeader; -use crate::{ - light_client_update::Error, test_utils::TestRandom, BeaconState, ChainSpec, SignedBeaconBlock, -}; +use crate::light_client_update::Error; +use crate::LightClientUpdate; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use ssz_derive::{Decode, Encode}; -use test_random_derive::TestRandom; -use tree_hash::TreeHash; /// A LightClientOptimisticUpdate is the update we send on each slot, /// it is based off the current unfinalized epoch is verified only against BLS signature. -#[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - TestRandom, - arbitrary::Arbitrary, -)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, arbitrary::Arbitrary)] #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] pub struct LightClientOptimisticUpdate { @@ -35,9 +21,7 @@ pub struct LightClientOptimisticUpdate { } impl LightClientOptimisticUpdate { - pub fn new( - update: LightClientUpdate, - ) -> Result { + pub fn new(update: LightClientUpdate) -> Result { Ok(Self { attested_header: update.attested_header, sync_aggregate: update.sync_aggregate, diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index bd4704a119d..df41ab4b384 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -1,7 +1,11 @@ use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee}; use crate::{ - beacon_state, test_utils::TestRandom, BeaconBlock, BeaconState, ChainSpec, ForkName, - ForkVersionDeserialize, LightClientHeader, SignedBeaconBlock, light_client_header::{LightClientHeaderDeneb, LightClientHeaderCapella, LightClientHeaderMerge}, + beacon_state, + light_client_header::{ + LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderMerge, + }, + BeaconBlock, BeaconState, ChainSpec, ForkName, ForkVersionDeserialize, LightClientHeader, + SignedBeaconBlock, }; use safe_arith::ArithError; use serde::{Deserialize, Deserializer, Serialize}; @@ -9,7 +13,6 @@ use serde_json::Value; use ssz_derive::{Decode, Encode}; use ssz_types::typenum::{U4, U5, U6}; use std::sync::Arc; -use test_random_derive::TestRandom; use tree_hash::TreeHash; pub const FINALIZED_ROOT_INDEX: usize = 105; @@ -61,17 +64,7 @@ impl From for Error { /// A LightClientUpdate is the update we request solely to either complete the bootstraping process, /// 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]. -#[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - TestRandom, - arbitrary::Arbitrary, -)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, arbitrary::Arbitrary)] #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] pub struct LightClientUpdate { @@ -139,7 +132,7 @@ impl LightClientUpdate { let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; if let Some(deneb_fork_epoch) = chain_spec.deneb_fork_epoch { - if beacon_state.slot().epoch(T::slots_per_epoch()) >= deneb_fork_epoch { + if beacon_state.slot().epoch(T::slots_per_epoch()) >= deneb_fork_epoch { return Ok(Self { attested_header: LightClientHeaderDeneb::new(attested_block)?.into(), next_sync_committee: attested_state.next_sync_committee()?.clone(), @@ -148,12 +141,12 @@ impl LightClientUpdate { finality_branch: FixedVector::new(finality_branch)?, sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), - }) + }); } }; if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { - if beacon_state.slot().epoch(T::slots_per_epoch()) >= capella_fork_epoch { + if beacon_state.slot().epoch(T::slots_per_epoch()) >= capella_fork_epoch { return Ok(Self { attested_header: LightClientHeaderCapella::new(attested_block)?.into(), next_sync_committee: attested_state.next_sync_committee()?.clone(), @@ -162,7 +155,7 @@ impl LightClientUpdate { finality_branch: FixedVector::new(finality_branch)?, sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), - }) + }); } }; From 17adb6a9b48f2e9631eaf1b7b9d08227b05a787d Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Sun, 3 Dec 2023 11:04:06 -0800 Subject: [PATCH 39/72] altair --- consensus/types/src/light_client_bootstrap.rs | 4 ++-- consensus/types/src/light_client_header.rs | 6 +++--- consensus/types/src/light_client_update.rs | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 5a69f18ce99..4e86e347dfd 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -1,7 +1,7 @@ use super::{BeaconState, EthSpec, FixedVector, Hash256, SyncCommittee}; use crate::{ light_client_header::{ - LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderMerge, + LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderAltair, }, light_client_update::*, ChainSpec, ForkName, ForkVersionDeserialize, LightClientHeader, SignedBeaconBlock, @@ -57,7 +57,7 @@ impl LightClientBootstrap { }; Ok(LightClientBootstrap { - header: LightClientHeaderMerge::new(block)?.into(), + header: LightClientHeaderAltair::new(block)?.into(), current_sync_committee: beacon_state.current_sync_committee()?.clone(), current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, }) diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 2cb3067e572..5f8b6698c2e 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -14,7 +14,7 @@ use test_random_derive::TestRandom; use tree_hash::TreeHash; #[superstruct( - variants(Merge, Capella, Deneb), + variants(Altair, Capella, Deneb), variant_attributes( derive( Default, @@ -53,9 +53,9 @@ pub struct LightClientHeader { pub phantom_data: PhantomData, } -impl LightClientHeaderMerge { +impl LightClientHeaderAltair { pub fn new(block: SignedBeaconBlock) -> Result { - Ok(LightClientHeaderMerge { + Ok(LightClientHeaderAltair { beacon: block.message().block_header(), phantom_data: PhantomData, }) diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index df41ab4b384..5f42c124832 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -2,7 +2,7 @@ use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, Slot, SyncAggregat use crate::{ beacon_state, light_client_header::{ - LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderMerge, + LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderAltair, }, BeaconBlock, BeaconState, ChainSpec, ForkName, ForkVersionDeserialize, LightClientHeader, SignedBeaconBlock, @@ -160,10 +160,10 @@ impl LightClientUpdate { }; Ok(Self { - attested_header: LightClientHeaderMerge::new(attested_block)?.into(), + attested_header: LightClientHeaderAltair::new(attested_block)?.into(), next_sync_committee: attested_state.next_sync_committee()?.clone(), next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?, - finalized_header: LightClientHeaderMerge::new(finalized_block)?.into(), + finalized_header: LightClientHeaderAltair::new(finalized_block)?.into(), finality_branch: FixedVector::new(finality_branch)?, sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), From 5ab7681e7f22a7d9f66b47a6ebeae2d7f30fd0f3 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Sun, 3 Dec 2023 11:06:42 -0800 Subject: [PATCH 40/72] update --- consensus/types/src/light_client_bootstrap.rs | 4 +-- consensus/types/src/light_client_header.rs | 6 ++-- consensus/types/src/light_client_update.rs | 30 ++++++++++++++----- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 4e86e347dfd..cd81b007eb6 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -1,7 +1,7 @@ use super::{BeaconState, EthSpec, FixedVector, Hash256, SyncCommittee}; use crate::{ light_client_header::{ - LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderAltair, + LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, }, light_client_update::*, ChainSpec, ForkName, ForkVersionDeserialize, LightClientHeader, SignedBeaconBlock, @@ -57,7 +57,7 @@ impl LightClientBootstrap { }; Ok(LightClientBootstrap { - header: LightClientHeaderAltair::new(block)?.into(), + header: LightClientHeaderAltair::block_to_light_client_header(block)?.into(), current_sync_committee: beacon_state.current_sync_committee()?.clone(), current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, }) diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 5f8b6698c2e..607fdcda077 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -54,7 +54,7 @@ pub struct LightClientHeader { } impl LightClientHeaderAltair { - pub fn new(block: SignedBeaconBlock) -> Result { + pub fn block_to_light_client_header(block: SignedBeaconBlock) -> Result { Ok(LightClientHeaderAltair { beacon: block.message().block_header(), phantom_data: PhantomData, @@ -73,7 +73,7 @@ impl LightClientHeaderAltair { } impl LightClientHeaderCapella { - pub fn new(block: SignedBeaconBlock) -> Result { + pub fn block_to_light_client_header(block: SignedBeaconBlock) -> Result { let payload = block .message() .execution_payload()? @@ -127,7 +127,7 @@ impl LightClientHeaderCapella { } impl LightClientHeaderDeneb { - pub fn new(block: SignedBeaconBlock) -> Result { + pub fn block_to_light_client_header(block: SignedBeaconBlock) -> Result { let payload = block .message() .execution_payload()? diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 5f42c124832..4ab89b5652b 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -2,7 +2,7 @@ use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, Slot, SyncAggregat use crate::{ beacon_state, light_client_header::{ - LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderAltair, + LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, }, BeaconBlock, BeaconState, ChainSpec, ForkName, ForkVersionDeserialize, LightClientHeader, SignedBeaconBlock, @@ -134,10 +134,16 @@ impl LightClientUpdate { if let Some(deneb_fork_epoch) = chain_spec.deneb_fork_epoch { if beacon_state.slot().epoch(T::slots_per_epoch()) >= deneb_fork_epoch { return Ok(Self { - attested_header: LightClientHeaderDeneb::new(attested_block)?.into(), + attested_header: LightClientHeaderDeneb::block_to_light_client_header( + attested_block, + )? + .into(), next_sync_committee: attested_state.next_sync_committee()?.clone(), next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?, - finalized_header: LightClientHeaderDeneb::new(finalized_block)?.into(), + finalized_header: LightClientHeaderDeneb::block_to_light_client_header( + finalized_block, + )? + .into(), finality_branch: FixedVector::new(finality_branch)?, sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), @@ -148,10 +154,16 @@ impl LightClientUpdate { if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { if beacon_state.slot().epoch(T::slots_per_epoch()) >= capella_fork_epoch { return Ok(Self { - attested_header: LightClientHeaderCapella::new(attested_block)?.into(), + attested_header: LightClientHeaderCapella::block_to_light_client_header( + attested_block, + )? + .into(), next_sync_committee: attested_state.next_sync_committee()?.clone(), next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?, - finalized_header: LightClientHeaderCapella::new(finalized_block)?.into(), + finalized_header: LightClientHeaderCapella::block_to_light_client_header( + finalized_block, + )? + .into(), finality_branch: FixedVector::new(finality_branch)?, sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), @@ -160,10 +172,14 @@ impl LightClientUpdate { }; Ok(Self { - attested_header: LightClientHeaderAltair::new(attested_block)?.into(), + attested_header: LightClientHeaderAltair::block_to_light_client_header(attested_block)? + .into(), next_sync_committee: attested_state.next_sync_committee()?.clone(), next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?, - finalized_header: LightClientHeaderAltair::new(finalized_block)?.into(), + finalized_header: LightClientHeaderAltair::block_to_light_client_header( + finalized_block, + )? + .into(), finality_branch: FixedVector::new(finality_branch)?, sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), From 74b0b6fcb9fb42f66af1e9864ba34ac0278c24bc Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Sun, 3 Dec 2023 11:07:05 -0800 Subject: [PATCH 41/72] update --- consensus/types/src/light_client_bootstrap.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index cd81b007eb6..653c5819b05 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -39,7 +39,7 @@ impl LightClientBootstrap { if let Some(deneb_fork_epoch) = chain_spec.deneb_fork_epoch { if beacon_state.slot().epoch(T::slots_per_epoch()) >= deneb_fork_epoch { return Ok(LightClientBootstrap { - header: LightClientHeaderDeneb::new(block)?.into(), + header: LightClientHeaderDeneb::block_to_light_client_header(block)?.into(), current_sync_committee: beacon_state.current_sync_committee()?.clone(), current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, }); @@ -49,7 +49,7 @@ impl LightClientBootstrap { if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { if beacon_state.slot().epoch(T::slots_per_epoch()) >= capella_fork_epoch { return Ok(LightClientBootstrap { - header: LightClientHeaderCapella::new(block)?.into(), + header: LightClientHeaderCapella::block_to_light_client_header(block)?.into(), current_sync_committee: beacon_state.current_sync_committee()?.clone(), current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, }); From 1879bfd5ce2560b0d61bac93e398fd760353e7db Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Sun, 3 Dec 2023 15:53:55 -0800 Subject: [PATCH 42/72] changes to light_client_optimistic_ and finality --- beacon_node/beacon_chain/src/beacon_chain.rs | 19 ++-- ...ght_client_finality_update_verification.rs | 28 ++++-- ...t_client_optimistic_update_verification.rs | 18 ++-- .../src/rpc/codec/ssz_snappy.rs | 2 +- .../lighthouse_network/src/rpc/methods.rs | 2 +- .../src/service/api_types.rs | 2 +- .../network_beacon_processor/rpc_methods.rs | 2 +- .../types/src/light_client_finality_update.rs | 89 +++++++++++++++++-- .../src/light_client_optimistic_update.rs | 63 +++++++++++-- 9 files changed, 186 insertions(+), 39 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 6b893d6967a..f26b2b91b12 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -6455,13 +6455,18 @@ impl BeaconChain { &self, block_root: &Hash256, ) -> Result, ForkName)>, Error> { - let Some((state_root, slot)) = self - .get_blinded_block(block_root)? - .map(|block| (block.state_root(), block.slot())) - else { + let runtime = tokio::runtime::Runtime::new().map_err(|_| Error::RuntimeShutdown)?; + + let Some(block) = runtime.block_on( async { + self + .get_block(block_root).await + } + )? else { return Ok(None); }; + let (state_root, slot) = (block.state_root(), block.slot()); + let Some(mut state) = self.get_state(&state_root, Some(slot))? else { return Ok(None); }; @@ -6471,12 +6476,12 @@ impl BeaconChain { .map_err(Error::InconsistentFork)?; match fork_name { - ForkName::Altair | ForkName::Merge => { - LightClientBootstrap::from_beacon_state(&mut state) + ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => { + LightClientBootstrap::from_beacon_state(&self.spec, &mut state, block) .map(|bootstrap| Some((bootstrap, fork_name))) .map_err(Error::LightClientError) } - ForkName::Base | ForkName::Capella | ForkName::Deneb => Err(Error::UnsupportedFork), + ForkName::Base => Err(Error::UnsupportedFork), } } } diff --git a/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs b/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs index 791d63ccfe5..2d0bac5d7f5 100644 --- a/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs +++ b/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs @@ -67,7 +67,8 @@ impl VerifiedLightClientFinalityUpdate { chain: &BeaconChain, seen_timestamp: Duration, ) -> Result { - let gossiped_finality_slot = light_client_finality_update.finalized_header.beacon.slot; + let runtime = tokio::runtime::Runtime::new().map_err(|_| Error::FailedConstructingUpdate)?; + let gossiped_finality_slot = light_client_finality_update.finalized_header.beacon().slot; let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0); let signature_slot = light_client_finality_update.signature_slot; let start_time = chain.slot_clock.start_of(signature_slot); @@ -76,19 +77,29 @@ impl VerifiedLightClientFinalityUpdate { let head = chain.canonical_head.cached_head(); let head_block = &head.snapshot.beacon_block; let attested_block_root = head_block.message().parent_root(); - let attested_block = chain - .get_blinded_block(&attested_block_root)? - .ok_or(Error::FailedConstructingUpdate)?; + + let attested_block = runtime.block_on( async { + chain + .get_block(&attested_block_root).await + } + )? + .ok_or(Error::FailedConstructingUpdate)?; + let mut attested_state = chain .get_state(&attested_block.state_root(), Some(attested_block.slot()))? .ok_or(Error::FailedConstructingUpdate)?; let finalized_block_root = attested_state.finalized_checkpoint().root; - let finalized_block = chain - .get_blinded_block(&finalized_block_root)? - .ok_or(Error::FailedConstructingUpdate)?; + + let finalized_block = runtime.block_on( async { + chain + .get_block(&finalized_block_root).await + } + )? + .ok_or(Error::FailedConstructingUpdate)?; + let latest_seen_finality_update_slot = match latest_seen_finality_update.as_ref() { - Some(update) => update.finalized_header.beacon.slot, + Some(update) => update.finalized_header.beacon().slot, None => Slot::new(0), }; @@ -116,6 +127,7 @@ impl VerifiedLightClientFinalityUpdate { head_state, head_block, &mut attested_state, + &attested_block, &finalized_block, )?; diff --git a/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs b/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs index 374cc9a7753..9400fd08078 100644 --- a/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs +++ b/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs @@ -71,7 +71,8 @@ impl VerifiedLightClientOptimisticUpdate { chain: &BeaconChain, seen_timestamp: Duration, ) -> Result { - let gossiped_optimistic_slot = light_client_optimistic_update.attested_header.beacon.slot; + let runtime = tokio::runtime::Runtime::new().map_err(|_| Error::FailedConstructingUpdate)?; + let gossiped_optimistic_slot = light_client_optimistic_update.attested_header.beacon().slot; let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0); let signature_slot = light_client_optimistic_update.signature_slot; let start_time = chain.slot_clock.start_of(signature_slot); @@ -80,15 +81,18 @@ impl VerifiedLightClientOptimisticUpdate { let head = chain.canonical_head.cached_head(); let head_block = &head.snapshot.beacon_block; let attested_block_root = head_block.message().parent_root(); - let attested_block = chain - .get_blinded_block(&attested_block_root)? - .ok_or(Error::FailedConstructingUpdate)?; + let attested_block = runtime.block_on( async { + chain + .get_block(&attested_block_root).await + } + )? + .ok_or(Error::FailedConstructingUpdate)?; let attested_state = chain .get_state(&attested_block.state_root(), Some(attested_block.slot()))? .ok_or(Error::FailedConstructingUpdate)?; let latest_seen_optimistic_update_slot = match latest_seen_optimistic_update.as_ref() { - Some(update) => update.attested_header.beacon.slot, + Some(update) => update.attested_header.beacon().slot, None => Slot::new(0), }; @@ -114,7 +118,7 @@ impl VerifiedLightClientOptimisticUpdate { // otherwise queue let canonical_root = light_client_optimistic_update .attested_header - .beacon + .beacon() .canonical_root(); if canonical_root != head_block.message().parent_root() { @@ -122,7 +126,7 @@ impl VerifiedLightClientOptimisticUpdate { } let optimistic_update = - LightClientOptimisticUpdate::new(&chain.spec, head_block, &attested_state)?; + LightClientOptimisticUpdate::new(&chain.spec, head_block, &attested_state, attested_block)?; // verify that the gossiped optimistic update is the same as the locally constructed one. if optimistic_update != light_client_optimistic_update { diff --git a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs index 787c3dcb7a5..6e58c281a00 100644 --- a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs +++ b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs @@ -576,7 +576,7 @@ fn handle_rpc_response( MetaDataV1::from_ssz_bytes(decoded_buffer)?, )))), SupportedProtocol::LightClientBootstrapV1 => Ok(Some(RPCResponse::LightClientBootstrap( - LightClientBootstrap::from_ssz_bytes(decoded_buffer)?, + Arc::new(LightClientBootstrap::from_ssz_bytes(decoded_buffer)?), ))), // MetaData V2 responses have no context bytes, so behave similarly to V1 responses SupportedProtocol::MetaDataV2 => Ok(Some(RPCResponse::MetaData(MetaData::V2( diff --git a/beacon_node/lighthouse_network/src/rpc/methods.rs b/beacon_node/lighthouse_network/src/rpc/methods.rs index e468ec8531d..52ea1e32a12 100644 --- a/beacon_node/lighthouse_network/src/rpc/methods.rs +++ b/beacon_node/lighthouse_network/src/rpc/methods.rs @@ -390,7 +390,7 @@ pub enum RPCResponse { BlobsByRange(Arc>), /// A response to a get LIGHTCLIENT_BOOTSTRAP request. - LightClientBootstrap(LightClientBootstrap), + LightClientBootstrap(Arc>), /// A response to a get BLOBS_BY_ROOT request. BlobsByRoot(Arc>), diff --git a/beacon_node/lighthouse_network/src/service/api_types.rs b/beacon_node/lighthouse_network/src/service/api_types.rs index 96c9d283327..1a16e4c70be 100644 --- a/beacon_node/lighthouse_network/src/service/api_types.rs +++ b/beacon_node/lighthouse_network/src/service/api_types.rs @@ -93,7 +93,7 @@ pub enum Response { /// A response to a get BLOBS_BY_ROOT request. BlobsByRoot(Option>>), /// A response to a LightClientUpdate request. - LightClientBootstrap(LightClientBootstrap), + LightClientBootstrap(Arc>), } impl std::convert::From> for RPCCodedResponse { diff --git a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs index 430e0571b7e..a4c23d25ee5 100644 --- a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs @@ -305,7 +305,7 @@ impl NetworkBeaconProcessor { match self.chain.get_light_client_bootstrap(&block_root) { Ok(Some((bootstrap, _))) => self.send_response( peer_id, - Response::LightClientBootstrap(bootstrap), + Response::LightClientBootstrap(Arc::new(bootstrap)), request_id, ), Ok(None) => self.send_error_response( diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index aebf9e84a04..032058eba49 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -1,8 +1,9 @@ use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate}; -use crate::{light_client_update::*, ForkName, ForkVersionDeserialize, LightClientHeader}; +use crate::{light_client_update::*, ForkName, ForkVersionDeserialize, LightClientHeader, ChainSpec, BeaconState, SignedBeaconBlock, light_client_header::{LightClientHeaderDeneb, LightClientHeaderCapella, LightClientHeaderAltair}}; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use ssz_derive::{Decode, Encode}; +use tree_hash::TreeHash; /// A LightClientFinalityUpdate is the update lightclient request or received by a gossip that /// signal a new finalized beacon block header for the light client sync protocol. @@ -23,13 +24,87 @@ pub struct LightClientFinalityUpdate { } impl LightClientFinalityUpdate { - pub fn new(update: LightClientUpdate) -> Result { + pub fn new( + chain_spec: &ChainSpec, + beacon_state: &BeaconState, + block: &SignedBeaconBlock, + attested_state: &mut BeaconState, + attested_block: &SignedBeaconBlock, + finalized_block: &SignedBeaconBlock + ) -> Result { + let altair_fork_epoch = chain_spec + .altair_fork_epoch + .ok_or(Error::AltairForkNotActive)?; + + if beacon_state.slot().epoch(T::slots_per_epoch()) < altair_fork_epoch { + return Err(Error::AltairForkNotActive); + } + + let sync_aggregate = block.message().body().sync_aggregate()?; + if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { + return Err(Error::NotEnoughSyncCommitteeParticipants); + } + + // Compute and validate attested header. + let mut attested_header = attested_state.latest_block_header().clone(); + attested_header.state_root = attested_state.update_tree_hash_cache()?; + // Build finalized header from finalized block + let finalized_header = finalized_block.message().block_header(); + + if finalized_header.tree_hash_root() != beacon_state.finalized_checkpoint().root { + return Err(Error::InvalidFinalizedBlock); + } + + let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; + + if let Some(deneb_fork_epoch) = chain_spec.deneb_fork_epoch { + if attested_state.slot().epoch(T::slots_per_epoch()) >= deneb_fork_epoch { + return Ok(Self { + attested_header: LightClientHeaderDeneb::block_to_light_client_header( + attested_block.to_owned(), + )? + .into(), + finalized_header: LightClientHeaderDeneb::block_to_light_client_header( + finalized_block.to_owned(), + )? + .into(), + finality_branch: FixedVector::new(finality_branch)?, + sync_aggregate: sync_aggregate.clone(), + signature_slot: block.slot(), + }) + } + } + + if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { + if attested_state.slot().epoch(T::slots_per_epoch()) >= capella_fork_epoch { + return Ok(Self { + attested_header: LightClientHeaderCapella::block_to_light_client_header( + attested_block.to_owned(), + )? + .into(), + finalized_header: LightClientHeaderCapella::block_to_light_client_header( + finalized_block.to_owned(), + )? + .into(), + finality_branch: FixedVector::new(finality_branch)?, + sync_aggregate: sync_aggregate.clone(), + signature_slot: block.slot(), + }) + } + } + Ok(Self { - attested_header: update.attested_header, - finalized_header: update.finalized_header, - finality_branch: update.finality_branch, - sync_aggregate: update.sync_aggregate, - signature_slot: update.signature_slot, + attested_header: LightClientHeaderAltair::block_to_light_client_header( + attested_block.to_owned(), + )? + .into(), + finalized_header: LightClientHeaderAltair::block_to_light_client_header( + finalized_block.to_owned(), + )? + .into(), + finality_branch: FixedVector::new(finality_branch)?, + sync_aggregate: sync_aggregate.clone(), + signature_slot: block.slot(), }) } } diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index 9150422cf52..3458eb103c0 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -1,10 +1,12 @@ use super::{EthSpec, ForkName, ForkVersionDeserialize, Slot, SyncAggregate}; -use crate::light_client_header::LightClientHeader; +use crate::light_client_header::{LightClientHeaderDeneb, LightClientHeaderCapella, LightClientHeaderAltair}; +use crate::{light_client_header::LightClientHeader, ChainSpec}; use crate::light_client_update::Error; -use crate::LightClientUpdate; +use crate::{SignedBeaconBlock, BeaconState}; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use ssz_derive::{Decode, Encode}; +use tree_hash::TreeHash; /// A LightClientOptimisticUpdate is the update we send on each slot, /// it is based off the current unfinalized epoch is verified only against BLS signature. @@ -21,11 +23,60 @@ pub struct LightClientOptimisticUpdate { } impl LightClientOptimisticUpdate { - pub fn new(update: LightClientUpdate) -> Result { + pub fn new( + chain_spec: &ChainSpec, + block: &SignedBeaconBlock, + attested_state: &BeaconState, + attested_block: SignedBeaconBlock, + ) -> Result { + let altair_fork_epoch = chain_spec + .altair_fork_epoch + .ok_or(Error::AltairForkNotActive)?; + if attested_state.slot().epoch(T::slots_per_epoch()) < altair_fork_epoch { + return Err(Error::AltairForkNotActive); + } + + let sync_aggregate = block.message().body().sync_aggregate()?; + if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { + return Err(Error::NotEnoughSyncCommitteeParticipants); + } + + // Compute and validate attested header. + let mut attested_header = attested_state.latest_block_header().clone(); + attested_header.state_root = attested_state.tree_hash_root(); + + if let Some(deneb_fork_epoch) = chain_spec.deneb_fork_epoch { + if attested_state.slot().epoch(T::slots_per_epoch()) >= deneb_fork_epoch { + return Ok(Self { + attested_header: LightClientHeaderDeneb::block_to_light_client_header( + attested_block, + )? + .into(), + sync_aggregate: sync_aggregate.clone(), + signature_slot: block.slot(), + }) + } + } + + if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { + if attested_state.slot().epoch(T::slots_per_epoch()) >= capella_fork_epoch { + return Ok(Self { + attested_header: LightClientHeaderCapella::block_to_light_client_header( + attested_block, + )? + .into(), + sync_aggregate: sync_aggregate.clone(), + signature_slot: block.slot(), + }) + } + } Ok(Self { - attested_header: update.attested_header, - sync_aggregate: update.sync_aggregate, - signature_slot: update.signature_slot, + attested_header: LightClientHeaderAltair::block_to_light_client_header( + attested_block, + )? + .into(), + sync_aggregate: sync_aggregate.clone(), + signature_slot: block.slot(), }) } } From 1d10abe930a06961c9c5ed5a7c977269690a8043 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Mon, 11 Dec 2023 18:45:53 -0800 Subject: [PATCH 43/72] refactor --- beacon_node/beacon_chain/src/beacon_chain.rs | 8 +- ...ght_client_finality_update_verification.rs | 21 ++-- ...t_client_optimistic_update_verification.rs | 20 +-- consensus/types/src/light_client_bootstrap.rs | 28 ++--- .../types/src/light_client_finality_update.rs | 115 ++++++++++-------- .../src/light_client_optimistic_update.rs | 57 +++------ consensus/types/src/light_client_update.rs | 79 +++++------- 7 files changed, 144 insertions(+), 184 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 00a95e7a29c..f82b60ac10c 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -6530,13 +6530,9 @@ impl BeaconChain { &self, block_root: &Hash256, ) -> Result, ForkName)>, Error> { - let runtime = tokio::runtime::Runtime::new().map_err(|_| Error::RuntimeShutdown)?; + let runtime = tokio::runtime::Runtime::new().map_err(|_| Error::RuntimeShutdown)?; - let Some(block) = runtime.block_on( async { - self - .get_block(block_root).await - } - )? else { + let Some(block) = runtime.block_on(async { self.get_block(block_root).await })? else { return Ok(None); }; diff --git a/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs b/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs index 2d0bac5d7f5..4be047bbf34 100644 --- a/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs +++ b/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs @@ -67,7 +67,8 @@ impl VerifiedLightClientFinalityUpdate { chain: &BeaconChain, seen_timestamp: Duration, ) -> Result { - let runtime = tokio::runtime::Runtime::new().map_err(|_| Error::FailedConstructingUpdate)?; + let runtime = + tokio::runtime::Runtime::new().map_err(|_| Error::FailedConstructingUpdate)?; let gossiped_finality_slot = light_client_finality_update.finalized_header.beacon().slot; let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0); let signature_slot = light_client_finality_update.signature_slot; @@ -78,12 +79,9 @@ impl VerifiedLightClientFinalityUpdate { let head_block = &head.snapshot.beacon_block; let attested_block_root = head_block.message().parent_root(); - let attested_block = runtime.block_on( async { - chain - .get_block(&attested_block_root).await - } - )? - .ok_or(Error::FailedConstructingUpdate)?; + let attested_block = runtime + .block_on(async { chain.get_block(&attested_block_root).await })? + .ok_or(Error::FailedConstructingUpdate)?; let mut attested_state = chain .get_state(&attested_block.state_root(), Some(attested_block.slot()))? @@ -91,12 +89,9 @@ impl VerifiedLightClientFinalityUpdate { let finalized_block_root = attested_state.finalized_checkpoint().root; - let finalized_block = runtime.block_on( async { - chain - .get_block(&finalized_block_root).await - } - )? - .ok_or(Error::FailedConstructingUpdate)?; + let finalized_block = runtime + .block_on(async { chain.get_block(&finalized_block_root).await })? + .ok_or(Error::FailedConstructingUpdate)?; let latest_seen_finality_update_slot = match latest_seen_finality_update.as_ref() { Some(update) => update.finalized_header.beacon().slot, diff --git a/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs b/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs index 9400fd08078..cd72869a738 100644 --- a/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs +++ b/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs @@ -71,7 +71,8 @@ impl VerifiedLightClientOptimisticUpdate { chain: &BeaconChain, seen_timestamp: Duration, ) -> Result { - let runtime = tokio::runtime::Runtime::new().map_err(|_| Error::FailedConstructingUpdate)?; + let runtime = + tokio::runtime::Runtime::new().map_err(|_| Error::FailedConstructingUpdate)?; let gossiped_optimistic_slot = light_client_optimistic_update.attested_header.beacon().slot; let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0); let signature_slot = light_client_optimistic_update.signature_slot; @@ -81,12 +82,9 @@ impl VerifiedLightClientOptimisticUpdate { let head = chain.canonical_head.cached_head(); let head_block = &head.snapshot.beacon_block; let attested_block_root = head_block.message().parent_root(); - let attested_block = runtime.block_on( async { - chain - .get_block(&attested_block_root).await - } - )? - .ok_or(Error::FailedConstructingUpdate)?; + let attested_block = runtime + .block_on(async { chain.get_block(&attested_block_root).await })? + .ok_or(Error::FailedConstructingUpdate)?; let attested_state = chain .get_state(&attested_block.state_root(), Some(attested_block.slot()))? @@ -125,8 +123,12 @@ impl VerifiedLightClientOptimisticUpdate { return Err(Error::UnknownBlockParentRoot(canonical_root)); } - let optimistic_update = - LightClientOptimisticUpdate::new(&chain.spec, head_block, &attested_state, attested_block)?; + let optimistic_update = LightClientOptimisticUpdate::new( + &chain.spec, + head_block, + &attested_state, + attested_block, + )?; // verify that the gossiped optimistic update is the same as the locally constructed one. if optimistic_update != light_client_optimistic_update { diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 653c5819b05..42d4e364d59 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -36,28 +36,22 @@ impl LightClientBootstrap { let current_sync_committee_branch = beacon_state.compute_merkle_proof(CURRENT_SYNC_COMMITTEE_INDEX)?; - if let Some(deneb_fork_epoch) = chain_spec.deneb_fork_epoch { - if beacon_state.slot().epoch(T::slots_per_epoch()) >= deneb_fork_epoch { - return Ok(LightClientBootstrap { - header: LightClientHeaderDeneb::block_to_light_client_header(block)?.into(), - current_sync_committee: beacon_state.current_sync_committee()?.clone(), - current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, - }); + let header: LightClientHeader = match chain_spec + .fork_name_at_epoch(beacon_state.slot().epoch(T::slots_per_epoch())) + { + ForkName::Base => return Err(Error::AltairForkNotActive), + ForkName::Merge => return Err(Error::AltairForkNotActive), + ForkName::Altair => { + LightClientHeaderAltair::block_to_light_client_header(block)?.into() } - }; - - if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { - if beacon_state.slot().epoch(T::slots_per_epoch()) >= capella_fork_epoch { - return Ok(LightClientBootstrap { - header: LightClientHeaderCapella::block_to_light_client_header(block)?.into(), - current_sync_committee: beacon_state.current_sync_committee()?.clone(), - current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, - }); + ForkName::Capella => { + LightClientHeaderCapella::block_to_light_client_header(block)?.into() } + ForkName::Deneb => LightClientHeaderDeneb::block_to_light_client_header(block)?.into(), }; Ok(LightClientBootstrap { - header: LightClientHeaderAltair::block_to_light_client_header(block)?.into(), + header, current_sync_committee: beacon_state.current_sync_committee()?.clone(), current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, }) diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index 032058eba49..64608980f8e 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -1,5 +1,11 @@ use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate}; -use crate::{light_client_update::*, ForkName, ForkVersionDeserialize, LightClientHeader, ChainSpec, BeaconState, SignedBeaconBlock, light_client_header::{LightClientHeaderDeneb, LightClientHeaderCapella, LightClientHeaderAltair}}; +use crate::{ + light_client_header::{ + LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, + }, + light_client_update::*, + BeaconState, ChainSpec, ForkName, ForkVersionDeserialize, LightClientHeader, SignedBeaconBlock, +}; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use ssz_derive::{Decode, Encode}; @@ -24,22 +30,14 @@ pub struct LightClientFinalityUpdate { } impl LightClientFinalityUpdate { - pub fn new( + pub fn new( chain_spec: &ChainSpec, beacon_state: &BeaconState, block: &SignedBeaconBlock, attested_state: &mut BeaconState, attested_block: &SignedBeaconBlock, - finalized_block: &SignedBeaconBlock + finalized_block: &SignedBeaconBlock, ) -> Result { - let altair_fork_epoch = chain_spec - .altair_fork_epoch - .ok_or(Error::AltairForkNotActive)?; - - if beacon_state.slot().epoch(T::slots_per_epoch()) < altair_fork_epoch { - return Err(Error::AltairForkNotActive); - } - let sync_aggregate = block.message().body().sync_aggregate()?; if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { return Err(Error::NotEnoughSyncCommitteeParticipants); @@ -57,51 +55,60 @@ impl LightClientFinalityUpdate { let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; - if let Some(deneb_fork_epoch) = chain_spec.deneb_fork_epoch { - if attested_state.slot().epoch(T::slots_per_epoch()) >= deneb_fork_epoch { - return Ok(Self { - attested_header: LightClientHeaderDeneb::block_to_light_client_header( - attested_block.to_owned(), - )? - .into(), - finalized_header: LightClientHeaderDeneb::block_to_light_client_header( - finalized_block.to_owned(), - )? - .into(), - finality_branch: FixedVector::new(finality_branch)?, - sync_aggregate: sync_aggregate.clone(), - signature_slot: block.slot(), - }) - } - } + let (attested_header, finalized_header) = + match chain_spec.fork_name_at_epoch(beacon_state.slot().epoch(T::slots_per_epoch())) { + ForkName::Base => return Err(Error::AltairForkNotActive), + ForkName::Merge => return Err(Error::AltairForkNotActive), + ForkName::Altair => { + let attested_header: LightClientHeader = + LightClientHeaderAltair::block_to_light_client_header( + attested_block.to_owned(), + )? + .into(); + + let finalized_header: LightClientHeader = + LightClientHeaderAltair::block_to_light_client_header( + finalized_block.to_owned(), + )? + .into(); + + (attested_header, finalized_header) + } + ForkName::Capella => { + let attested_header: LightClientHeader = + LightClientHeaderCapella::block_to_light_client_header( + attested_block.to_owned(), + )? + .into(); + + let finalized_header: LightClientHeader = + LightClientHeaderCapella::block_to_light_client_header( + finalized_block.to_owned(), + )? + .into(); + + (attested_header, finalized_header) + } + ForkName::Deneb => { + let attested_header: LightClientHeader = + LightClientHeaderDeneb::block_to_light_client_header( + attested_block.to_owned(), + )? + .into(); + + let finalized_header: LightClientHeader = + LightClientHeaderDeneb::block_to_light_client_header( + finalized_block.to_owned(), + )? + .into(); + + (attested_header, finalized_header) + } + }; - if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { - if attested_state.slot().epoch(T::slots_per_epoch()) >= capella_fork_epoch { - return Ok(Self { - attested_header: LightClientHeaderCapella::block_to_light_client_header( - attested_block.to_owned(), - )? - .into(), - finalized_header: LightClientHeaderCapella::block_to_light_client_header( - finalized_block.to_owned(), - )? - .into(), - finality_branch: FixedVector::new(finality_branch)?, - sync_aggregate: sync_aggregate.clone(), - signature_slot: block.slot(), - }) - } - } - Ok(Self { - attested_header: LightClientHeaderAltair::block_to_light_client_header( - attested_block.to_owned(), - )? - .into(), - finalized_header: LightClientHeaderAltair::block_to_light_client_header( - finalized_block.to_owned(), - )? - .into(), + attested_header, + finalized_header, finality_branch: FixedVector::new(finality_branch)?, sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index 3458eb103c0..4e905b85412 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -1,8 +1,10 @@ use super::{EthSpec, ForkName, ForkVersionDeserialize, Slot, SyncAggregate}; -use crate::light_client_header::{LightClientHeaderDeneb, LightClientHeaderCapella, LightClientHeaderAltair}; -use crate::{light_client_header::LightClientHeader, ChainSpec}; +use crate::light_client_header::{ + LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, +}; use crate::light_client_update::Error; -use crate::{SignedBeaconBlock, BeaconState}; +use crate::{light_client_header::LightClientHeader, ChainSpec}; +use crate::{BeaconState, SignedBeaconBlock}; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use ssz_derive::{Decode, Encode}; @@ -29,13 +31,6 @@ impl LightClientOptimisticUpdate { attested_state: &BeaconState, attested_block: SignedBeaconBlock, ) -> Result { - let altair_fork_epoch = chain_spec - .altair_fork_epoch - .ok_or(Error::AltairForkNotActive)?; - if attested_state.slot().epoch(T::slots_per_epoch()) < altair_fork_epoch { - return Err(Error::AltairForkNotActive); - } - let sync_aggregate = block.message().body().sync_aggregate()?; if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { return Err(Error::NotEnoughSyncCommitteeParticipants); @@ -45,36 +40,24 @@ impl LightClientOptimisticUpdate { let mut attested_header = attested_state.latest_block_header().clone(); attested_header.state_root = attested_state.tree_hash_root(); - if let Some(deneb_fork_epoch) = chain_spec.deneb_fork_epoch { - if attested_state.slot().epoch(T::slots_per_epoch()) >= deneb_fork_epoch { - return Ok(Self { - attested_header: LightClientHeaderDeneb::block_to_light_client_header( - attested_block, - )? - .into(), - sync_aggregate: sync_aggregate.clone(), - signature_slot: block.slot(), - }) + let attested_header: LightClientHeader = match chain_spec + .fork_name_at_epoch(attested_state.slot().epoch(T::slots_per_epoch())) + { + ForkName::Base => return Err(Error::AltairForkNotActive), + ForkName::Merge => return Err(Error::AltairForkNotActive), + ForkName::Altair => { + LightClientHeaderAltair::block_to_light_client_header(attested_block)?.into() } - } - - if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { - if attested_state.slot().epoch(T::slots_per_epoch()) >= capella_fork_epoch { - return Ok(Self { - attested_header: LightClientHeaderCapella::block_to_light_client_header( - attested_block, - )? - .into(), - sync_aggregate: sync_aggregate.clone(), - signature_slot: block.slot(), - }) + ForkName::Capella => { + LightClientHeaderCapella::block_to_light_client_header(attested_block)?.into() } - } + ForkName::Deneb => { + LightClientHeaderDeneb::block_to_light_client_header(attested_block)?.into() + } + }; + Ok(Self { - attested_header: LightClientHeaderAltair::block_to_light_client_header( - attested_block, - )? - .into(), + attested_header, sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), }) diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 4ab89b5652b..be0d091f4a3 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -93,13 +93,6 @@ impl LightClientUpdate { attested_block: SignedBeaconBlock, finalized_block: SignedBeaconBlock, ) -> Result { - let altair_fork_epoch = chain_spec - .altair_fork_epoch - .ok_or(Error::AltairForkNotActive)?; - if attested_state.slot().epoch(T::slots_per_epoch()) < altair_fork_epoch { - return Err(Error::AltairForkNotActive); - } - let sync_aggregate = block.body().sync_aggregate()?; if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { return Err(Error::NotEnoughSyncCommitteeParticipants); @@ -131,55 +124,45 @@ impl LightClientUpdate { attested_state.compute_merkle_proof(NEXT_SYNC_COMMITTEE_INDEX)?; let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; - if let Some(deneb_fork_epoch) = chain_spec.deneb_fork_epoch { - if beacon_state.slot().epoch(T::slots_per_epoch()) >= deneb_fork_epoch { - return Ok(Self { - attested_header: LightClientHeaderDeneb::block_to_light_client_header( - attested_block, - )? - .into(), - next_sync_committee: attested_state.next_sync_committee()?.clone(), - next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?, - finalized_header: LightClientHeaderDeneb::block_to_light_client_header( - finalized_block, - )? - .into(), - finality_branch: FixedVector::new(finality_branch)?, - sync_aggregate: sync_aggregate.clone(), - signature_slot: block.slot(), - }); + let (attested_header, finalized_header) = match chain_spec + .fork_name_at_epoch(beacon_state.slot().epoch(T::slots_per_epoch())) + { + ForkName::Base => return Err(Error::AltairForkNotActive), + ForkName::Merge => return Err(Error::AltairForkNotActive), + ForkName::Altair => { + let attested_header: LightClientHeader = + LightClientHeaderAltair::block_to_light_client_header(attested_block)?.into(); + + let finalized_header: LightClientHeader = + LightClientHeaderAltair::block_to_light_client_header(finalized_block)?.into(); + + (attested_header, finalized_header) } - }; + ForkName::Capella => { + let attested_header: LightClientHeader = + LightClientHeaderCapella::block_to_light_client_header(attested_block)?.into(); + + let finalized_header: LightClientHeader = + LightClientHeaderCapella::block_to_light_client_header(finalized_block)?.into(); + + (attested_header, finalized_header) + } + ForkName::Deneb => { + let attested_header: LightClientHeader = + LightClientHeaderDeneb::block_to_light_client_header(attested_block)?.into(); + + let finalized_header: LightClientHeader = + LightClientHeaderDeneb::block_to_light_client_header(finalized_block)?.into(); - if let Some(capella_fork_epoch) = chain_spec.capella_fork_epoch { - if beacon_state.slot().epoch(T::slots_per_epoch()) >= capella_fork_epoch { - return Ok(Self { - attested_header: LightClientHeaderCapella::block_to_light_client_header( - attested_block, - )? - .into(), - next_sync_committee: attested_state.next_sync_committee()?.clone(), - next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?, - finalized_header: LightClientHeaderCapella::block_to_light_client_header( - finalized_block, - )? - .into(), - finality_branch: FixedVector::new(finality_branch)?, - sync_aggregate: sync_aggregate.clone(), - signature_slot: block.slot(), - }); + (attested_header, finalized_header) } }; Ok(Self { - attested_header: LightClientHeaderAltair::block_to_light_client_header(attested_block)? - .into(), + attested_header, next_sync_committee: attested_state.next_sync_committee()?.clone(), next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?, - finalized_header: LightClientHeaderAltair::block_to_light_client_header( - finalized_block, - )? - .into(), + finalized_header, finality_branch: FixedVector::new(finality_branch)?, sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), From b75d23b4d0f29ffdbd351ed79a6b1020cb9ac668 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Thu, 1 Feb 2024 11:51:30 +0200 Subject: [PATCH 44/72] block_to_light_client_header fork aware --- beacon_node/beacon_chain/src/light_client_server_cache.rs | 5 +++-- consensus/types/src/light_client_header.rs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/beacon_node/beacon_chain/src/light_client_server_cache.rs b/beacon_node/beacon_chain/src/light_client_server_cache.rs index 0a7514a186d..b8ca5ca7fc4 100644 --- a/beacon_node/beacon_chain/src/light_client_server_cache.rs +++ b/beacon_node/beacon_chain/src/light_client_server_cache.rs @@ -12,6 +12,7 @@ use types::non_zero_usize::new_non_zero_usize; use types::{ BeaconBlockRef, BeaconState, ChainSpec, EthSpec, ForkName, Hash256, LightClientFinalityUpdate, LightClientHeader, LightClientOptimisticUpdate, SignedBeaconBlock, Slot, SyncAggregate, + light_client_update::Error as LightClientError }; /// A prev block cache miss requires to re-generate the state of the post-parent block. Items in the @@ -259,8 +260,8 @@ fn block_to_light_client_header( block: SignedBeaconBlock, ) -> Result, BeaconChainError> { let light_client_header = match fork_name { - ForkName::Base => todo!(), - ForkName::Merge => todo!(), + ForkName::Base => return Err(LightClientError::AltairForkNotActive.into()), + ForkName::Merge => return Err(LightClientError::AltairForkNotActive.into()), ForkName::Altair => LightClientHeaderAltair::block_to_light_client_header(block)?.into(), ForkName::Capella => LightClientHeaderCapella::block_to_light_client_header(block)?.into(), ForkName::Deneb => LightClientHeaderDeneb::block_to_light_client_header(block)?.into(), diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 7a9f5ede142..1bc01cc43a8 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -26,7 +26,7 @@ use tree_hash::TreeHash; Encode, Decode, TestRandom, - arbitrary::Arbitrary + arbitrary::Arbitrary, ), serde(bound = "E: EthSpec", deny_unknown_fields), arbitrary(bound = "E: EthSpec"), From 5f75f25820f98a91e9b80a3ba3f017acfc29a7f9 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Thu, 1 Feb 2024 11:51:40 +0200 Subject: [PATCH 45/72] fmt --- beacon_node/beacon_chain/src/light_client_server_cache.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/beacon_node/beacon_chain/src/light_client_server_cache.rs b/beacon_node/beacon_chain/src/light_client_server_cache.rs index b8ca5ca7fc4..d2bfdbae670 100644 --- a/beacon_node/beacon_chain/src/light_client_server_cache.rs +++ b/beacon_node/beacon_chain/src/light_client_server_cache.rs @@ -10,9 +10,9 @@ use types::light_client_header::{ use types::light_client_update::{FinalizedRootProofLen, FINALIZED_ROOT_INDEX}; use types::non_zero_usize::new_non_zero_usize; use types::{ - BeaconBlockRef, BeaconState, ChainSpec, EthSpec, ForkName, Hash256, LightClientFinalityUpdate, - LightClientHeader, LightClientOptimisticUpdate, SignedBeaconBlock, Slot, SyncAggregate, - light_client_update::Error as LightClientError + light_client_update::Error as LightClientError, BeaconBlockRef, BeaconState, ChainSpec, + EthSpec, ForkName, Hash256, LightClientFinalityUpdate, LightClientHeader, + LightClientOptimisticUpdate, SignedBeaconBlock, Slot, SyncAggregate, }; /// A prev block cache miss requires to re-generate the state of the post-parent block. Items in the From 96f89a25485d549de1cbfb8edfa0fcfa00498b8b Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Thu, 1 Feb 2024 12:32:41 +0200 Subject: [PATCH 46/72] comment fix --- beacon_node/beacon_chain/src/light_client_server_cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/src/light_client_server_cache.rs b/beacon_node/beacon_chain/src/light_client_server_cache.rs index d2bfdbae670..ce07ffc32de 100644 --- a/beacon_node/beacon_chain/src/light_client_server_cache.rs +++ b/beacon_node/beacon_chain/src/light_client_server_cache.rs @@ -239,7 +239,7 @@ fn is_latest_finality_update( } } -// Implements spec priorization rules: +// Implements spec prioritization rules: // > Full nodes SHOULD provide the LightClientOptimisticUpdate with the highest attested_header.beacon.slot (if multiple, highest signature_slot) // // ref: https://github.com/ethereum/consensus-specs/blob/113c58f9bf9c08867f6f5f633c4d98e0364d612a/specs/altair/light-client/full-node.md#create_light_client_optimistic_update From 7a253bf1dc54910d2b87309f38da3aa2db99be2b Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Thu, 1 Feb 2024 12:33:16 +0200 Subject: [PATCH 47/72] comment fix --- beacon_node/beacon_chain/src/light_client_server_cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/src/light_client_server_cache.rs b/beacon_node/beacon_chain/src/light_client_server_cache.rs index ce07ffc32de..f3852d23796 100644 --- a/beacon_node/beacon_chain/src/light_client_server_cache.rs +++ b/beacon_node/beacon_chain/src/light_client_server_cache.rs @@ -223,7 +223,7 @@ impl LightClientCachedData { } } -// Implements spec priorization rules: +// Implements spec prioritization rules: // > Full nodes SHOULD provide the LightClientFinalityUpdate with the highest attested_header.beacon.slot (if multiple, highest signature_slot) // // ref: https://github.com/ethereum/consensus-specs/blob/113c58f9bf9c08867f6f5f633c4d98e0364d612a/specs/altair/light-client/full-node.md#create_light_client_finality_update From 5808f7ccdb27159575fac138ff0e26a6cb279041 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Sat, 3 Feb 2024 17:42:16 +0200 Subject: [PATCH 48/72] include merge fork, update deserialize_by_fork, refactor --- .../src/light_client_server_cache.rs | 5 +- consensus/types/src/light_client_bootstrap.rs | 15 ++- .../types/src/light_client_finality_update.rs | 91 +++++++------------ .../src/light_client_optimistic_update.rs | 7 +- consensus/types/src/light_client_update.rs | 53 ++++------- 5 files changed, 61 insertions(+), 110 deletions(-) diff --git a/beacon_node/beacon_chain/src/light_client_server_cache.rs b/beacon_node/beacon_chain/src/light_client_server_cache.rs index f3852d23796..c503eaa6c13 100644 --- a/beacon_node/beacon_chain/src/light_client_server_cache.rs +++ b/beacon_node/beacon_chain/src/light_client_server_cache.rs @@ -261,8 +261,9 @@ fn block_to_light_client_header( ) -> Result, BeaconChainError> { let light_client_header = match fork_name { ForkName::Base => return Err(LightClientError::AltairForkNotActive.into()), - ForkName::Merge => return Err(LightClientError::AltairForkNotActive.into()), - ForkName::Altair => LightClientHeaderAltair::block_to_light_client_header(block)?.into(), + ForkName::Merge | ForkName::Altair => { + LightClientHeaderAltair::block_to_light_client_header(block)?.into() + } ForkName::Capella => LightClientHeaderCapella::block_to_light_client_header(block)?.into(), ForkName::Deneb => LightClientHeaderDeneb::block_to_light_client_header(block)?.into(), }; diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index e10ce4f7d2a..ec68ab7390b 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -40,8 +40,7 @@ impl LightClientBootstrap { .fork_name_at_epoch(beacon_state.slot().epoch(T::slots_per_epoch())) { ForkName::Base => return Err(Error::AltairForkNotActive), - ForkName::Merge => return Err(Error::AltairForkNotActive), - ForkName::Altair => { + ForkName::Altair | ForkName::Merge => { LightClientHeaderAltair::block_to_light_client_header(block)?.into() } ForkName::Capella => { @@ -64,16 +63,14 @@ impl ForkVersionDeserialize for LightClientBootstrap { fork_name: ForkName, ) -> Result { match fork_name { - ForkName::Altair | ForkName::Merge => { + ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => { Ok(serde_json::from_value::>(value) .map_err(serde::de::Error::custom))? } - ForkName::Base | ForkName::Capella | ForkName::Deneb => { - Err(serde::de::Error::custom(format!( - "LightClientBootstrap failed to deserialize: unsupported fork '{}'", - fork_name - ))) - } + ForkName::Base => Err(serde::de::Error::custom(format!( + "LightClientBootstrap failed to deserialize: unsupported fork '{}'", + fork_name + ))), } } } diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index f38f5e356d9..da9b1989ece 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -55,56 +55,29 @@ impl LightClientFinalityUpdate { let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; - let (attested_header, finalized_header) = - match chain_spec.fork_name_at_epoch(beacon_state.slot().epoch(T::slots_per_epoch())) { - ForkName::Base => return Err(Error::AltairForkNotActive), - ForkName::Merge => return Err(Error::AltairForkNotActive), - ForkName::Altair => { - let attested_header: LightClientHeader = - LightClientHeaderAltair::block_to_light_client_header( - attested_block.to_owned(), - )? - .into(); - - let finalized_header: LightClientHeader = - LightClientHeaderAltair::block_to_light_client_header( - finalized_block.to_owned(), - )? - .into(); - - (attested_header, finalized_header) - } - ForkName::Capella => { - let attested_header: LightClientHeader = - LightClientHeaderCapella::block_to_light_client_header( - attested_block.to_owned(), - )? - .into(); - - let finalized_header: LightClientHeader = - LightClientHeaderCapella::block_to_light_client_header( - finalized_block.to_owned(), - )? - .into(); - - (attested_header, finalized_header) - } - ForkName::Deneb => { - let attested_header: LightClientHeader = - LightClientHeaderDeneb::block_to_light_client_header( - attested_block.to_owned(), - )? - .into(); - - let finalized_header: LightClientHeader = - LightClientHeaderDeneb::block_to_light_client_header( - finalized_block.to_owned(), - )? - .into(); - - (attested_header, finalized_header) - } - }; + let (attested_header, finalized_header) = match chain_spec + .fork_name_at_epoch(beacon_state.slot().epoch(T::slots_per_epoch())) + { + ForkName::Base => return Err(Error::AltairForkNotActive), + ForkName::Altair | ForkName::Merge => ( + LightClientHeaderAltair::block_to_light_client_header(attested_block.to_owned())? + .into(), + LightClientHeaderAltair::block_to_light_client_header(finalized_block.to_owned())? + .into(), + ), + ForkName::Capella => ( + LightClientHeaderCapella::block_to_light_client_header(attested_block.to_owned())? + .into(), + LightClientHeaderCapella::block_to_light_client_header(finalized_block.to_owned())? + .into(), + ), + ForkName::Deneb => ( + LightClientHeaderDeneb::block_to_light_client_header(attested_block.to_owned())? + .into(), + LightClientHeaderDeneb::block_to_light_client_header(finalized_block.to_owned())? + .into(), + ), + }; Ok(Self { attested_header, @@ -122,16 +95,14 @@ impl ForkVersionDeserialize for LightClientFinalityUpdate { fork_name: ForkName, ) -> Result { match fork_name { - ForkName::Altair | ForkName::Merge => Ok(serde_json::from_value::< - LightClientFinalityUpdate, - >(value) - .map_err(serde::de::Error::custom))?, - ForkName::Base | ForkName::Capella | ForkName::Deneb => { - Err(serde::de::Error::custom(format!( - "LightClientFinalityUpdate failed to deserialize: unsupported fork '{}'", - fork_name - ))) - } + ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => Ok( + serde_json::from_value::>(value) + .map_err(serde::de::Error::custom), + )?, + ForkName::Base => Err(serde::de::Error::custom(format!( + "LightClientFinalityUpdate failed to deserialize: unsupported fork '{}'", + fork_name + ))), } } } diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index 7c346b5ded0..bc7663cfca9 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -44,8 +44,7 @@ impl LightClientOptimisticUpdate { .fork_name_at_epoch(attested_state.slot().epoch(T::slots_per_epoch())) { ForkName::Base => return Err(Error::AltairForkNotActive), - ForkName::Merge => return Err(Error::AltairForkNotActive), - ForkName::Altair => { + ForkName::Merge | ForkName::Altair => { LightClientHeaderAltair::block_to_light_client_header(attested_block)?.into() } ForkName::Capella => { @@ -70,11 +69,11 @@ impl ForkVersionDeserialize for LightClientOptimisticUpdate { fork_name: ForkName, ) -> Result { match fork_name { - ForkName::Altair | ForkName::Merge => Ok(serde_json::from_value::< + ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => Ok(serde_json::from_value::< LightClientOptimisticUpdate, >(value) .map_err(serde::de::Error::custom))?, - ForkName::Base | ForkName::Capella | ForkName::Deneb => { + ForkName::Base => { Err(serde::de::Error::custom(format!( "LightClientOptimisticUpdate failed to deserialize: unsupported fork '{}'", fork_name diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index be53479df5e..c689a453415 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -124,39 +124,22 @@ impl LightClientUpdate { attested_state.compute_merkle_proof(NEXT_SYNC_COMMITTEE_INDEX)?; let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; - let (attested_header, finalized_header) = match chain_spec - .fork_name_at_epoch(beacon_state.slot().epoch(T::slots_per_epoch())) - { - ForkName::Base => return Err(Error::AltairForkNotActive), - ForkName::Merge => return Err(Error::AltairForkNotActive), - ForkName::Altair => { - let attested_header: LightClientHeader = - LightClientHeaderAltair::block_to_light_client_header(attested_block)?.into(); - - let finalized_header: LightClientHeader = - LightClientHeaderAltair::block_to_light_client_header(finalized_block)?.into(); - - (attested_header, finalized_header) - } - ForkName::Capella => { - let attested_header: LightClientHeader = - LightClientHeaderCapella::block_to_light_client_header(attested_block)?.into(); - - let finalized_header: LightClientHeader = - LightClientHeaderCapella::block_to_light_client_header(finalized_block)?.into(); - - (attested_header, finalized_header) - } - ForkName::Deneb => { - let attested_header: LightClientHeader = - LightClientHeaderDeneb::block_to_light_client_header(attested_block)?.into(); - - let finalized_header: LightClientHeader = - LightClientHeaderDeneb::block_to_light_client_header(finalized_block)?.into(); - - (attested_header, finalized_header) - } - }; + let (attested_header, finalized_header) = + match chain_spec.fork_name_at_epoch(beacon_state.slot().epoch(T::slots_per_epoch())) { + ForkName::Base => return Err(Error::AltairForkNotActive), + ForkName::Merge | ForkName::Altair => ( + LightClientHeaderAltair::block_to_light_client_header(attested_block)?.into(), + LightClientHeaderAltair::block_to_light_client_header(finalized_block)?.into(), + ), + ForkName::Capella => ( + LightClientHeaderCapella::block_to_light_client_header(attested_block)?.into(), + LightClientHeaderCapella::block_to_light_client_header(finalized_block)?.into(), + ), + ForkName::Deneb => ( + LightClientHeaderDeneb::block_to_light_client_header(attested_block)?.into(), + LightClientHeaderDeneb::block_to_light_client_header(finalized_block)?.into(), + ), + }; Ok(Self { attested_header, @@ -176,11 +159,11 @@ impl ForkVersionDeserialize for LightClientUpdate { fork_name: ForkName, ) -> Result { match fork_name { - ForkName::Altair | ForkName::Merge => { + ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => { Ok(serde_json::from_value::>(value) .map_err(serde::de::Error::custom))? } - ForkName::Base | ForkName::Capella | ForkName::Deneb => { + ForkName::Base => { Err(serde::de::Error::custom(format!( "LightClientUpdate failed to deserialize: unsupported fork '{}'", fork_name From 6663614af9578c6b72b9603633b4490d76ddb092 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Sat, 3 Feb 2024 17:42:33 +0200 Subject: [PATCH 49/72] fmt --- .../src/light_client_optimistic_update.rs | 18 ++++++++---------- consensus/types/src/light_client_update.rs | 10 ++++------ 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index bc7663cfca9..219430b0d16 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -69,16 +69,14 @@ impl ForkVersionDeserialize for LightClientOptimisticUpdate { fork_name: ForkName, ) -> Result { match fork_name { - ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => Ok(serde_json::from_value::< - LightClientOptimisticUpdate, - >(value) - .map_err(serde::de::Error::custom))?, - ForkName::Base => { - Err(serde::de::Error::custom(format!( - "LightClientOptimisticUpdate failed to deserialize: unsupported fork '{}'", - fork_name - ))) - } + ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => Ok( + serde_json::from_value::>(value) + .map_err(serde::de::Error::custom), + )?, + ForkName::Base => Err(serde::de::Error::custom(format!( + "LightClientOptimisticUpdate failed to deserialize: unsupported fork '{}'", + fork_name + ))), } } } diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index c689a453415..1c8517affea 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -163,12 +163,10 @@ impl ForkVersionDeserialize for LightClientUpdate { Ok(serde_json::from_value::>(value) .map_err(serde::de::Error::custom))? } - ForkName::Base => { - Err(serde::de::Error::custom(format!( - "LightClientUpdate failed to deserialize: unsupported fork '{}'", - fork_name - ))) - } + ForkName::Base => Err(serde::de::Error::custom(format!( + "LightClientUpdate failed to deserialize: unsupported fork '{}'", + fork_name + ))), } } } From f442b5a844d6193ef252d0d5b6bb98e7fbe1295e Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Sat, 3 Feb 2024 17:55:10 +0200 Subject: [PATCH 50/72] pass by ref to prevent clone --- beacon_node/beacon_chain/src/beacon_chain.rs | 2 +- .../beacon_chain/src/light_client_server_cache.rs | 8 ++++---- consensus/types/src/light_client_bootstrap.rs | 2 +- consensus/types/src/light_client_finality_update.rs | 12 ++++++------ consensus/types/src/light_client_header.rs | 6 +++--- .../types/src/light_client_optimistic_update.rs | 2 +- consensus/types/src/light_client_update.rs | 4 ++-- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 754611f13d9..d4edefcd5a7 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -6650,7 +6650,7 @@ impl BeaconChain { match fork_name { ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => { - LightClientBootstrap::from_beacon_state(&self.spec, &mut state, block) + LightClientBootstrap::from_beacon_state(&self.spec, &mut state, &block) .map(|bootstrap| Some((bootstrap, fork_name))) .map_err(Error::LightClientError) } diff --git a/beacon_node/beacon_chain/src/light_client_server_cache.rs b/beacon_node/beacon_chain/src/light_client_server_cache.rs index c503eaa6c13..7cb80dd3730 100644 --- a/beacon_node/beacon_chain/src/light_client_server_cache.rs +++ b/beacon_node/beacon_chain/src/light_client_server_cache.rs @@ -119,7 +119,7 @@ impl LightClientServerCache { if is_latest_optimistic { // can create an optimistic update, that is more recent *self.latest_optimistic_update.write() = Some(LightClientOptimisticUpdate { - attested_header: block_to_light_client_header(fork_name, attested_block.clone())?, + attested_header: block_to_light_client_header(fork_name, &attested_block)?, sync_aggregate: sync_aggregate.clone(), signature_slot, }); @@ -141,8 +141,8 @@ impl LightClientServerCache { *self.latest_finality_update.write() = Some(LightClientFinalityUpdate { // TODO: may want to cache this result from latest_optimistic_update if producing a // light_client header becomes expensive - attested_header: block_to_light_client_header(fork_name, attested_block)?, - finalized_header: block_to_light_client_header(fork_name, finalized_block)?, + attested_header: block_to_light_client_header(fork_name, &attested_block)?, + finalized_header: block_to_light_client_header(fork_name, &finalized_block)?, finality_branch: cached_parts.finality_branch.clone(), sync_aggregate: sync_aggregate.clone(), signature_slot, @@ -257,7 +257,7 @@ fn is_latest_optimistic_update( fn block_to_light_client_header( fork_name: ForkName, - block: SignedBeaconBlock, + block: &SignedBeaconBlock, ) -> Result, BeaconChainError> { let light_client_header = match fork_name { ForkName::Base => return Err(LightClientError::AltairForkNotActive.into()), diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index ec68ab7390b..26a239101a1 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -29,7 +29,7 @@ impl LightClientBootstrap { pub fn from_beacon_state( chain_spec: &ChainSpec, beacon_state: &mut BeaconState, - block: SignedBeaconBlock, + block: &SignedBeaconBlock, ) -> Result { let mut header = beacon_state.latest_block_header().clone(); header.state_root = beacon_state.update_tree_hash_cache()?; diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index da9b1989ece..c7d3b966c2e 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -60,21 +60,21 @@ impl LightClientFinalityUpdate { { ForkName::Base => return Err(Error::AltairForkNotActive), ForkName::Altair | ForkName::Merge => ( - LightClientHeaderAltair::block_to_light_client_header(attested_block.to_owned())? + LightClientHeaderAltair::block_to_light_client_header(attested_block)? .into(), - LightClientHeaderAltair::block_to_light_client_header(finalized_block.to_owned())? + LightClientHeaderAltair::block_to_light_client_header(finalized_block)? .into(), ), ForkName::Capella => ( - LightClientHeaderCapella::block_to_light_client_header(attested_block.to_owned())? + LightClientHeaderCapella::block_to_light_client_header(attested_block)? .into(), - LightClientHeaderCapella::block_to_light_client_header(finalized_block.to_owned())? + LightClientHeaderCapella::block_to_light_client_header(finalized_block)? .into(), ), ForkName::Deneb => ( - LightClientHeaderDeneb::block_to_light_client_header(attested_block.to_owned())? + LightClientHeaderDeneb::block_to_light_client_header(attested_block)? .into(), - LightClientHeaderDeneb::block_to_light_client_header(finalized_block.to_owned())? + LightClientHeaderDeneb::block_to_light_client_header(finalized_block)? .into(), ), }; diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 1bc01cc43a8..35df1db66de 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -54,7 +54,7 @@ pub struct LightClientHeader { } impl LightClientHeaderAltair { - pub fn block_to_light_client_header(block: SignedBeaconBlock) -> Result { + pub fn block_to_light_client_header(block: &SignedBeaconBlock) -> Result { Ok(LightClientHeaderAltair { beacon: block.message().block_header(), phantom_data: PhantomData, @@ -73,7 +73,7 @@ impl LightClientHeaderAltair { } impl LightClientHeaderCapella { - pub fn block_to_light_client_header(block: SignedBeaconBlock) -> Result { + pub fn block_to_light_client_header(block: &SignedBeaconBlock) -> Result { let payload = block .message() .execution_payload()? @@ -127,7 +127,7 @@ impl LightClientHeaderCapella { } impl LightClientHeaderDeneb { - pub fn block_to_light_client_header(block: SignedBeaconBlock) -> Result { + pub fn block_to_light_client_header(block: &SignedBeaconBlock) -> Result { let payload = block .message() .execution_payload()? diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index 219430b0d16..8b7201de6a3 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -29,7 +29,7 @@ impl LightClientOptimisticUpdate { chain_spec: &ChainSpec, block: &SignedBeaconBlock, attested_state: &BeaconState, - attested_block: SignedBeaconBlock, + attested_block: &SignedBeaconBlock, ) -> Result { let sync_aggregate = block.message().body().sync_aggregate()?; if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 1c8517affea..c29e9c1e116 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -90,8 +90,8 @@ impl LightClientUpdate { beacon_state: BeaconState, block: BeaconBlock, attested_state: &mut BeaconState, - attested_block: SignedBeaconBlock, - finalized_block: SignedBeaconBlock, + attested_block: &SignedBeaconBlock, + finalized_block: &SignedBeaconBlock, ) -> Result { let sync_aggregate = block.body().sync_aggregate()?; if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { From 932f440dab2886d8fb14022aad288be6a8186eb0 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Sat, 3 Feb 2024 18:11:55 +0200 Subject: [PATCH 51/72] rename merkle proof fn --- consensus/types/src/beacon_block_body.rs | 2 +- .../types/src/light_client_finality_update.rs | 39 ++++++++----------- consensus/types/src/light_client_header.rs | 6 ++- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index 906a1332c68..b12f57a2249 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -595,7 +595,7 @@ impl From>> } impl BeaconBlockBody { - pub fn compute_merkle_proof( + pub fn block_body_merkle_proof( &mut self, generalized_index: usize, ) -> Result, Error> { diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index c7d3b966c2e..a5cdc80c6a0 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -55,29 +55,22 @@ impl LightClientFinalityUpdate { let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; - let (attested_header, finalized_header) = match chain_spec - .fork_name_at_epoch(beacon_state.slot().epoch(T::slots_per_epoch())) - { - ForkName::Base => return Err(Error::AltairForkNotActive), - ForkName::Altair | ForkName::Merge => ( - LightClientHeaderAltair::block_to_light_client_header(attested_block)? - .into(), - LightClientHeaderAltair::block_to_light_client_header(finalized_block)? - .into(), - ), - ForkName::Capella => ( - LightClientHeaderCapella::block_to_light_client_header(attested_block)? - .into(), - LightClientHeaderCapella::block_to_light_client_header(finalized_block)? - .into(), - ), - ForkName::Deneb => ( - LightClientHeaderDeneb::block_to_light_client_header(attested_block)? - .into(), - LightClientHeaderDeneb::block_to_light_client_header(finalized_block)? - .into(), - ), - }; + let (attested_header, finalized_header) = + match chain_spec.fork_name_at_epoch(beacon_state.slot().epoch(T::slots_per_epoch())) { + ForkName::Base => return Err(Error::AltairForkNotActive), + ForkName::Altair | ForkName::Merge => ( + LightClientHeaderAltair::block_to_light_client_header(attested_block)?.into(), + LightClientHeaderAltair::block_to_light_client_header(finalized_block)?.into(), + ), + ForkName::Capella => ( + LightClientHeaderCapella::block_to_light_client_header(attested_block)?.into(), + LightClientHeaderCapella::block_to_light_client_header(finalized_block)?.into(), + ), + ForkName::Deneb => ( + LightClientHeaderDeneb::block_to_light_client_header(attested_block)?.into(), + LightClientHeaderDeneb::block_to_light_client_header(finalized_block)?.into(), + ), + }; Ok(Self { attested_header, diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 35df1db66de..456b8df38ca 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -89,7 +89,8 @@ impl LightClientHeaderCapella { .to_owned(), ); - let execution_branch = beacon_block_body.compute_merkle_proof(EXECUTION_PAYLOAD_INDEX)?; + let execution_branch = + beacon_block_body.block_body_merkle_proof(EXECUTION_PAYLOAD_INDEX)?; return Ok(LightClientHeaderCapella { beacon: block.message().block_header(), @@ -143,7 +144,8 @@ impl LightClientHeaderDeneb { .to_owned(), ); - let execution_branch = beacon_block_body.compute_merkle_proof(EXECUTION_PAYLOAD_INDEX)?; + let execution_branch = + beacon_block_body.block_body_merkle_proof(EXECUTION_PAYLOAD_INDEX)?; Ok(LightClientHeaderDeneb { beacon: block.message().block_header(), From 55568911e3ae9f3df0e9b21f873a70e5f7fc90a3 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Mon, 5 Feb 2024 16:58:51 +0200 Subject: [PATCH 52/72] add FIXME --- beacon_node/lighthouse_network/src/rpc/methods.rs | 2 +- consensus/types/src/light_client_bootstrap.rs | 1 + consensus/types/src/light_client_finality_update.rs | 2 +- consensus/types/src/light_client_optimistic_update.rs | 2 +- consensus/types/src/light_client_update.rs | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/beacon_node/lighthouse_network/src/rpc/methods.rs b/beacon_node/lighthouse_network/src/rpc/methods.rs index 72903ad8e14..415a3ce9c92 100644 --- a/beacon_node/lighthouse_network/src/rpc/methods.rs +++ b/beacon_node/lighthouse_network/src/rpc/methods.rs @@ -384,7 +384,7 @@ pub enum RPCResponse { /// A response to a get BLOBS_BY_RANGE request BlobsByRange(Arc>), - /// A response to a get LIGHTCLIENT_BOOTSTRAP request. + /// A response to a get LIGHT_CLIENT_BOOTSTRAP request. LightClientBootstrap(Arc>), /// A response to a get BLOBS_BY_ROOT request. diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 26a239101a1..91b85d78de0 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -75,6 +75,7 @@ impl ForkVersionDeserialize for LightClientBootstrap { } } +// FIXME type includes enum which isn't compatible w/ TestRandom derive // #[cfg(test)] // mod tests { // use super::*; diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index a5cdc80c6a0..bf8e0b72d9c 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -99,7 +99,7 @@ impl ForkVersionDeserialize for LightClientFinalityUpdate { } } } - +// FIXME type includes enum which isn't compatible w/ TestRandom derive // #[cfg(test)] // mod tests { // use super::*; diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index 8b7201de6a3..bbcdfcbb52c 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -80,7 +80,7 @@ impl ForkVersionDeserialize for LightClientOptimisticUpdate { } } } - +// FIXME type includes enum which isn't compatible w/ TestRandom derive // #[cfg(test)] // mod tests { // use super::*; diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index c29e9c1e116..4899702b683 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -176,7 +176,7 @@ mod tests { use super::*; // use crate::MainnetEthSpec; use ssz_types::typenum::Unsigned; - + // FIXME type includes enum which isn't compatible w/ TestRandom derive // ssz_tests!(LightClientUpdate); #[test] From 06da24a0f1b6f5a66dffe5da8611583a56f166a6 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Tue, 6 Feb 2024 18:02:42 +0200 Subject: [PATCH 53/72] LightClientHeader TestRandom --- consensus/types/src/light_client_bootstrap.rs | 27 ++++++++++++------ .../types/src/light_client_finality_update.rs | 28 +++++++++++++------ .../src/light_client_optimistic_update.rs | 28 +++++++++++++------ consensus/types/src/light_client_update.rs | 20 ++++++++++--- consensus/types/src/test_utils/test_random.rs | 1 + .../test_random/light_client_header.rs | 10 +++++++ 6 files changed, 86 insertions(+), 28 deletions(-) create mode 100644 consensus/types/src/test_utils/test_random/light_client_header.rs diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 91b85d78de0..2e96d9d2af1 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -4,16 +4,28 @@ use crate::{ LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, }, light_client_update::*, + test_utils::TestRandom, ChainSpec, ForkName, ForkVersionDeserialize, LightClientHeader, SignedBeaconBlock, }; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use ssz_derive::{Decode, Encode}; use std::sync::Arc; +use test_random_derive::TestRandom; /// A LightClientBootstrap is the initializer we send over to light_client nodes /// that are trying to generate their basic storage when booting up. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, arbitrary::Arbitrary)] +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + arbitrary::Arbitrary, + TestRandom, +)] #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] pub struct LightClientBootstrap { @@ -75,11 +87,10 @@ impl ForkVersionDeserialize for LightClientBootstrap { } } -// FIXME type includes enum which isn't compatible w/ TestRandom derive -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::MainnetEthSpec; +#[cfg(test)] +mod tests { + use super::*; + use crate::MainnetEthSpec; -// ssz_tests!(LightClientBootstrap); -// } + ssz_tests!(LightClientBootstrap); +} diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index bf8e0b72d9c..ee0059bac66 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -4,16 +4,28 @@ use crate::{ LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, }, light_client_update::*, + test_utils::TestRandom, BeaconState, ChainSpec, ForkName, ForkVersionDeserialize, LightClientHeader, SignedBeaconBlock, }; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; use tree_hash::TreeHash; /// A LightClientFinalityUpdate is the update light_client request or received by a gossip that /// signal a new finalized beacon block header for the light client sync protocol. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, arbitrary::Arbitrary)] +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + arbitrary::Arbitrary, + TestRandom, +)] #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] pub struct LightClientFinalityUpdate { @@ -99,11 +111,11 @@ impl ForkVersionDeserialize for LightClientFinalityUpdate { } } } -// FIXME type includes enum which isn't compatible w/ TestRandom derive -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::MainnetEthSpec; -// ssz_tests!(LightClientFinalityUpdate); -// } +#[cfg(test)] +mod tests { + use super::*; + use crate::MainnetEthSpec; + + ssz_tests!(LightClientFinalityUpdate); +} diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index bbcdfcbb52c..a3217fbeb9a 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -3,16 +3,28 @@ use crate::light_client_header::{ LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, }; use crate::light_client_update::Error; +use crate::test_utils::TestRandom; use crate::{light_client_header::LightClientHeader, ChainSpec}; use crate::{BeaconState, SignedBeaconBlock}; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; use tree_hash::TreeHash; /// A LightClientOptimisticUpdate is the update we send on each slot, /// it is based off the current unfinalized epoch is verified only against BLS signature. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, arbitrary::Arbitrary)] +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + arbitrary::Arbitrary, + TestRandom, +)] #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] pub struct LightClientOptimisticUpdate { @@ -80,11 +92,11 @@ impl ForkVersionDeserialize for LightClientOptimisticUpdate { } } } -// FIXME type includes enum which isn't compatible w/ TestRandom derive -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::MainnetEthSpec; -// ssz_tests!(LightClientOptimisticUpdate); -// } +#[cfg(test)] +mod tests { + use super::*; + use crate::MainnetEthSpec; + + ssz_tests!(LightClientOptimisticUpdate); +} diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 4899702b683..0a72894c052 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -4,6 +4,7 @@ use crate::{ light_client_header::{ LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, }, + test_utils::TestRandom, BeaconBlock, BeaconState, ChainSpec, ForkName, ForkVersionDeserialize, LightClientHeader, SignedBeaconBlock, }; @@ -13,6 +14,7 @@ use serde_json::Value; use ssz_derive::{Decode, Encode}; use ssz_types::typenum::{U4, U5, U6}; use std::sync::Arc; +use test_random_derive::TestRandom; use tree_hash::TreeHash; pub const FINALIZED_ROOT_INDEX: usize = 105; @@ -64,7 +66,17 @@ impl From for Error { /// A LightClientUpdate is the update we request solely to either complete the bootstraping process, /// 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]. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, arbitrary::Arbitrary)] +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + arbitrary::Arbitrary, + TestRandom, +)] #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] pub struct LightClientUpdate { @@ -174,10 +186,10 @@ impl ForkVersionDeserialize for LightClientUpdate { #[cfg(test)] mod tests { use super::*; - // use crate::MainnetEthSpec; + use crate::MainnetEthSpec; use ssz_types::typenum::Unsigned; - // FIXME type includes enum which isn't compatible w/ TestRandom derive - // ssz_tests!(LightClientUpdate); + + ssz_tests!(LightClientUpdate); #[test] fn finalized_root_params() { diff --git a/consensus/types/src/test_utils/test_random.rs b/consensus/types/src/test_utils/test_random.rs index f31df2ce1b6..9881b694e7b 100644 --- a/consensus/types/src/test_utils/test_random.rs +++ b/consensus/types/src/test_utils/test_random.rs @@ -12,6 +12,7 @@ mod bitfield; mod hash256; mod kzg_commitment; mod kzg_proof; +mod light_client_header; mod public_key; mod public_key_bytes; mod secret_key; diff --git a/consensus/types/src/test_utils/test_random/light_client_header.rs b/consensus/types/src/test_utils/test_random/light_client_header.rs new file mode 100644 index 00000000000..bff1f0004e2 --- /dev/null +++ b/consensus/types/src/test_utils/test_random/light_client_header.rs @@ -0,0 +1,10 @@ +use super::*; +use crate::{light_client_header::LightClientHeaderDeneb, LightClientHeader}; + +/// Implements `TestRandom` for the `LightClientHeader`` superstruct. +/// We choose `LightClientHeaderDeneb`` since it is a superset of all other variants +impl TestRandom for LightClientHeader { + fn random_for_test(rng: &mut impl RngCore) -> Self { + LightClientHeaderDeneb::::random_for_test(rng).into() + } +} From 453b4d24151726f52a1ac1b17ce6d52589f4029a Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Tue, 6 Feb 2024 18:10:17 +0200 Subject: [PATCH 54/72] fix comments --- .../types/src/test_utils/test_random/light_client_header.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/consensus/types/src/test_utils/test_random/light_client_header.rs b/consensus/types/src/test_utils/test_random/light_client_header.rs index bff1f0004e2..6a97aff0cd2 100644 --- a/consensus/types/src/test_utils/test_random/light_client_header.rs +++ b/consensus/types/src/test_utils/test_random/light_client_header.rs @@ -1,8 +1,8 @@ use super::*; use crate::{light_client_header::LightClientHeaderDeneb, LightClientHeader}; -/// Implements `TestRandom` for the `LightClientHeader`` superstruct. -/// We choose `LightClientHeaderDeneb`` since it is a superset of all other variants +/// Implements `TestRandom` for the `LightClientHeader` superstruct +/// We choose `LightClientHeaderDeneb` since it is a superset of all other variants impl TestRandom for LightClientHeader { fn random_for_test(rng: &mut impl RngCore) -> Self { LightClientHeaderDeneb::::random_for_test(rng).into() From d97d496f75edac92169881fde2fed792179ad7f5 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Thu, 29 Feb 2024 18:37:53 +0200 Subject: [PATCH 55/72] fork version deserialize --- consensus/cached_tree_hash/src/impls.rs | 4 ++-- consensus/types/src/light_client_header.rs | 25 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/consensus/cached_tree_hash/src/impls.rs b/consensus/cached_tree_hash/src/impls.rs index 0624bd20145..b2a640301d7 100644 --- a/consensus/cached_tree_hash/src/impls.rs +++ b/consensus/cached_tree_hash/src/impls.rs @@ -26,13 +26,13 @@ pub fn u64_leaf_count(len: usize) -> usize { pub fn hash256_iter( values: &[Hash256], -) -> impl Iterator + ExactSizeIterator + '_ { +) -> impl ExactSizeIterator + '_{ values.iter().copied().map(Hash256::to_fixed_bytes) } pub fn u64_iter( values: &[u64], -) -> impl Iterator + ExactSizeIterator + '_ { +) -> impl ExactSizeIterator + '_ { let type_size = size_of::(); let vals_per_chunk = BYTES_PER_CHUNK / type_size; values.chunks(vals_per_chunk).map(move |xs| { diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 456b8df38ca..c3f2ceb48fb 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,4 +1,6 @@ use crate::beacon_block_body::NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES; +use crate::ForkVersionDeserialize; +use crate::ForkName; use crate::BeaconBlockHeader; use crate::{light_client_update::*, BeaconBlockBody}; use crate::{ @@ -12,6 +14,7 @@ use std::marker::PhantomData; use superstruct::superstruct; use test_random_derive::TestRandom; use tree_hash::TreeHash; +use serde_json; #[superstruct( variants(Altair, Capella, Deneb), @@ -181,3 +184,25 @@ impl LightClientHeaderDeneb { )) } } + +impl ForkVersionDeserialize for LightClientHeader { + fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( + value: serde_json::value::Value, + fork_name: ForkName, + ) -> Result { + match fork_name { + ForkName::Altair => serde_json::from_value(value) + .map(|light_client_header| Self::Altair(light_client_header)) + .map_err(serde::de::Error::custom), + ForkName::Capella => serde_json::from_value(value) + .map(|light_client_header| Self::Capella(light_client_header)) + .map_err(serde::de::Error::custom), + ForkName::Deneb => serde_json::from_value(value) + .map(|light_client_header| Self::Deneb(light_client_header)) + .map_err(serde::de::Error::custom), + ForkName::Base | ForkName::Merge => Err(serde::de::Error::custom(format!( + "LightClientHeader deserialization for {fork_name} not implemented" + ))), + } + } +} From b23c913605ccb4d565a3f687377954aaac6b7d47 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Sat, 2 Mar 2024 21:26:07 +0200 Subject: [PATCH 56/72] move fn arguments, fork name calc --- beacon_node/beacon_chain/src/beacon_chain.rs | 6 +++--- beacon_node/beacon_chain/src/light_client_server_cache.rs | 7 +++---- consensus/types/src/light_client_bootstrap.rs | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 0774c0067fa..34e7847d51e 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -1348,12 +1348,12 @@ impl BeaconChain { (parent_root, slot, sync_aggregate): LightClientProducerEvent, ) -> Result<(), Error> { self.light_client_server_cache.recompute_and_cache_updates( - &self.log, - &self.spec, self.store.clone(), &parent_root, slot, &sync_aggregate, + &self.log, + &self.spec, ) } @@ -6636,7 +6636,7 @@ impl BeaconChain { match fork_name { ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => { - LightClientBootstrap::from_beacon_state(&self.spec, &mut state, &block) + LightClientBootstrap::from_beacon_state(&mut state, &block, &self.spec) .map(|bootstrap| Some((bootstrap, fork_name))) .map_err(Error::LightClientError) } diff --git a/beacon_node/beacon_chain/src/light_client_server_cache.rs b/beacon_node/beacon_chain/src/light_client_server_cache.rs index 7cb80dd3730..f44b44ca38f 100644 --- a/beacon_node/beacon_chain/src/light_client_server_cache.rs +++ b/beacon_node/beacon_chain/src/light_client_server_cache.rs @@ -75,12 +75,12 @@ impl LightClientServerCache { /// results are cached either on disk or memory to be served via p2p and rest API pub fn recompute_and_cache_updates( &self, - log: &Logger, - chain_spec: &ChainSpec, store: BeaconStore, block_parent_root: &Hash256, block_slot: Slot, sync_aggregate: &SyncAggregate, + log: &Logger, + chain_spec: &ChainSpec, ) -> Result<(), BeaconChainError> { let _timer = metrics::start_timer(&metrics::LIGHT_CLIENT_SERVER_CACHE_RECOMPUTE_UPDATES_TIMES); @@ -105,8 +105,7 @@ impl LightClientServerCache { let attested_slot = attested_block.slot(); - let fork_name = - chain_spec.fork_name_at_epoch(attested_slot.epoch(T::EthSpec::slots_per_epoch())); + let fork_name = attested_block.fork_name(chain_spec)?; // Spec: Full nodes SHOULD provide the LightClientOptimisticUpdate with the highest // attested_header.beacon.slot (if multiple, highest signature_slot) as selected by fork choice diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 2e96d9d2af1..4b1995e8b10 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -39,9 +39,9 @@ pub struct LightClientBootstrap { impl LightClientBootstrap { pub fn from_beacon_state( - chain_spec: &ChainSpec, beacon_state: &mut BeaconState, block: &SignedBeaconBlock, + chain_spec: &ChainSpec, ) -> Result { let mut header = beacon_state.latest_block_header().clone(); header.state_root = beacon_state.update_tree_hash_cache()?; From dde9274240098e9c25d59b4961c904c85aa4d4c4 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Mon, 4 Mar 2024 01:22:09 +0200 Subject: [PATCH 57/72] use task executor --- beacon_node/beacon_chain/src/beacon_chain.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 34e7847d51e..9feae713094 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -6618,9 +6618,12 @@ impl BeaconChain { &self, block_root: &Hash256, ) -> Result, ForkName)>, Error> { - let runtime = tokio::runtime::Runtime::new().map_err(|_| Error::RuntimeShutdown)?; + let handle = self + .task_executor + .handle() + .ok_or(BeaconChainError::RuntimeShutdown)?; - let Some(block) = runtime.block_on(async { self.get_block(block_root).await })? else { + let Some(block) = handle.block_on(async { self.get_block(block_root).await })? else { return Ok(None); }; From 38cdd77be695d194ad0ebc65fcc1156526779416 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Wed, 6 Mar 2024 00:28:26 +0200 Subject: [PATCH 58/72] remove unneeded fns --- .../types/src/light_client_finality_update.rs | 59 +--------------- consensus/types/src/light_client_header.rs | 69 +------------------ 2 files changed, 3 insertions(+), 125 deletions(-) diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index ee0059bac66..ac69986a825 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -1,17 +1,13 @@ use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate}; use crate::{ - light_client_header::{ - LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, - }, light_client_update::*, test_utils::TestRandom, - BeaconState, ChainSpec, ForkName, ForkVersionDeserialize, LightClientHeader, SignedBeaconBlock, + ForkName, ForkVersionDeserialize, LightClientHeader }; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; -use tree_hash::TreeHash; /// A LightClientFinalityUpdate is the update light_client request or received by a gossip that /// signal a new finalized beacon block header for the light client sync protocol. @@ -41,59 +37,6 @@ pub struct LightClientFinalityUpdate { pub signature_slot: Slot, } -impl LightClientFinalityUpdate { - pub fn new( - chain_spec: &ChainSpec, - beacon_state: &BeaconState, - block: &SignedBeaconBlock, - attested_state: &mut BeaconState, - attested_block: &SignedBeaconBlock, - finalized_block: &SignedBeaconBlock, - ) -> Result { - let sync_aggregate = block.message().body().sync_aggregate()?; - if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { - return Err(Error::NotEnoughSyncCommitteeParticipants); - } - - // Compute and validate attested header. - let mut attested_header = attested_state.latest_block_header().clone(); - attested_header.state_root = attested_state.update_tree_hash_cache()?; - // Build finalized header from finalized block - let finalized_header = finalized_block.message().block_header(); - - if finalized_header.tree_hash_root() != beacon_state.finalized_checkpoint().root { - return Err(Error::InvalidFinalizedBlock); - } - - let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; - - let (attested_header, finalized_header) = - match chain_spec.fork_name_at_epoch(beacon_state.slot().epoch(T::slots_per_epoch())) { - ForkName::Base => return Err(Error::AltairForkNotActive), - ForkName::Altair | ForkName::Merge => ( - LightClientHeaderAltair::block_to_light_client_header(attested_block)?.into(), - LightClientHeaderAltair::block_to_light_client_header(finalized_block)?.into(), - ), - ForkName::Capella => ( - LightClientHeaderCapella::block_to_light_client_header(attested_block)?.into(), - LightClientHeaderCapella::block_to_light_client_header(finalized_block)?.into(), - ), - ForkName::Deneb => ( - LightClientHeaderDeneb::block_to_light_client_header(attested_block)?.into(), - LightClientHeaderDeneb::block_to_light_client_header(finalized_block)?.into(), - ), - }; - - Ok(Self { - attested_header, - finalized_header, - finality_branch: FixedVector::new(finality_branch)?, - sync_aggregate: sync_aggregate.clone(), - signature_slot: block.slot(), - }) - } -} - impl ForkVersionDeserialize for LightClientFinalityUpdate { fn deserialize_by_fork<'de, D: Deserializer<'de>>( value: Value, diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 182d3dc26fc..64d912bc08a 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,4 +1,3 @@ -use crate::beacon_block_body::NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES; use crate::BeaconBlockHeader; use crate::ForkName; use crate::ForkVersionDeserialize; @@ -7,14 +6,12 @@ use crate::{ test_utils::TestRandom, EthSpec, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, FixedVector, Hash256, SignedBeaconBlock, }; -use merkle_proof::verify_merkle_proof; use serde::{Deserialize, Serialize}; use serde_json; use ssz_derive::{Decode, Encode}; use std::marker::PhantomData; use superstruct::superstruct; use test_random_derive::TestRandom; -use tree_hash::TreeHash; #[superstruct( variants(Altair, Capella, Deneb), @@ -63,16 +60,6 @@ impl LightClientHeaderAltair { phantom_data: PhantomData, }) } - - #[allow(dead_code)] - fn get_lc_execution_root(&self) -> Option { - None - } - - #[allow(dead_code)] - fn is_valid_light_client_header(&self) -> Result { - Ok(true) - } } impl LightClientHeaderCapella { @@ -102,32 +89,6 @@ impl LightClientHeaderCapella { phantom_data: PhantomData, }); } - - #[allow(dead_code)] - fn get_lc_execution_root(&self) -> Option { - Some(self.execution.tree_hash_root()) - } - - #[allow(dead_code)] - fn is_valid_light_client_header(&self) -> Result { - let Some(execution_root) = self.get_lc_execution_root() else { - return Ok(false); - }; - - let Some(field_index) = - EXECUTION_PAYLOAD_INDEX.checked_sub(NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES) - else { - return Ok(false); - }; - - Ok(verify_merkle_proof( - execution_root, - &self.execution_branch, - EXECUTION_PAYLOAD_PROOF_LEN, - field_index, - self.beacon.body_root, - )) - } } impl LightClientHeaderDeneb { @@ -157,32 +118,6 @@ impl LightClientHeaderDeneb { phantom_data: PhantomData, }) } - - #[allow(dead_code)] - fn get_lc_execution_root(&self) -> Option { - Some(self.execution.tree_hash_root()) - } - - #[allow(dead_code)] - fn is_valid_light_client_header(&self) -> Result { - let Some(execution_root) = self.get_lc_execution_root() else { - return Ok(false); - }; - - let Some(field_index) = - EXECUTION_PAYLOAD_INDEX.checked_sub(NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES) - else { - return Ok(false); - }; - - Ok(verify_merkle_proof( - execution_root, - &self.execution_branch, - EXECUTION_PAYLOAD_PROOF_LEN, - field_index, - self.beacon.body_root, - )) - } } impl ForkVersionDeserialize for LightClientHeader { @@ -191,7 +126,7 @@ impl ForkVersionDeserialize for LightClientHeader { fork_name: ForkName, ) -> Result { match fork_name { - ForkName::Altair => serde_json::from_value(value) + ForkName::Altair | ForkName::Merge => serde_json::from_value(value) .map(|light_client_header| Self::Altair(light_client_header)) .map_err(serde::de::Error::custom), ForkName::Capella => serde_json::from_value(value) @@ -200,7 +135,7 @@ impl ForkVersionDeserialize for LightClientHeader { ForkName::Deneb => serde_json::from_value(value) .map(|light_client_header| Self::Deneb(light_client_header)) .map_err(serde::de::Error::custom), - ForkName::Base | ForkName::Merge => Err(serde::de::Error::custom(format!( + ForkName::Base => Err(serde::de::Error::custom(format!( "LightClientHeader deserialization for {fork_name} not implemented" ))), } From c064c45dabc7f5d84e495e5162016a7be621f6aa Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Wed, 6 Mar 2024 12:19:10 +0200 Subject: [PATCH 59/72] remove dead code --- consensus/types/src/light_client_header.rs | 4 +- .../src/light_client_optimistic_update.rs | 47 +---------- consensus/types/src/light_client_update.rs | 78 +------------------ 3 files changed, 5 insertions(+), 124 deletions(-) diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 64d912bc08a..58554904768 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -17,7 +17,6 @@ use test_random_derive::TestRandom; variants(Altair, Capella, Deneb), variant_attributes( derive( - Default, Debug, Clone, PartialEq, @@ -32,7 +31,7 @@ use test_random_derive::TestRandom; arbitrary(bound = "E: EthSpec"), ) )] -#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, arbitrary::Arbitrary, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, Decode, Encode, arbitrary::Arbitrary, PartialEq)] #[serde(untagged)] #[serde(bound = "E: EthSpec", deny_unknown_fields)] #[ssz(enum_behaviour = "union")] @@ -49,6 +48,7 @@ pub struct LightClientHeader { #[superstruct(only(Deneb), partial_getter(rename = "execution_payload_header_deneb"))] pub execution: ExecutionPayloadHeaderDeneb, + #[serde(skip)] #[ssz(skip_serializing, skip_deserializing)] pub phantom_data: PhantomData, } diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index a3217fbeb9a..39be0d0f1b0 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -1,16 +1,10 @@ use super::{EthSpec, ForkName, ForkVersionDeserialize, Slot, SyncAggregate}; -use crate::light_client_header::{ - LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, -}; -use crate::light_client_update::Error; use crate::test_utils::TestRandom; -use crate::{light_client_header::LightClientHeader, ChainSpec}; -use crate::{BeaconState, SignedBeaconBlock}; +use crate::{light_client_header::LightClientHeader}; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; -use tree_hash::TreeHash; /// A LightClientOptimisticUpdate is the update we send on each slot, /// it is based off the current unfinalized epoch is verified only against BLS signature. @@ -36,45 +30,6 @@ pub struct LightClientOptimisticUpdate { pub signature_slot: Slot, } -impl LightClientOptimisticUpdate { - pub fn new( - chain_spec: &ChainSpec, - block: &SignedBeaconBlock, - attested_state: &BeaconState, - attested_block: &SignedBeaconBlock, - ) -> Result { - let sync_aggregate = block.message().body().sync_aggregate()?; - if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { - return Err(Error::NotEnoughSyncCommitteeParticipants); - } - - // Compute and validate attested header. - let mut attested_header = attested_state.latest_block_header().clone(); - attested_header.state_root = attested_state.tree_hash_root(); - - let attested_header: LightClientHeader = match chain_spec - .fork_name_at_epoch(attested_state.slot().epoch(T::slots_per_epoch())) - { - ForkName::Base => return Err(Error::AltairForkNotActive), - ForkName::Merge | ForkName::Altair => { - LightClientHeaderAltair::block_to_light_client_header(attested_block)?.into() - } - ForkName::Capella => { - LightClientHeaderCapella::block_to_light_client_header(attested_block)?.into() - } - ForkName::Deneb => { - LightClientHeaderDeneb::block_to_light_client_header(attested_block)?.into() - } - }; - - Ok(Self { - attested_header, - sync_aggregate: sync_aggregate.clone(), - signature_slot: block.slot(), - }) - } -} - impl ForkVersionDeserialize for LightClientOptimisticUpdate { fn deserialize_by_fork<'de, D: Deserializer<'de>>( value: Value, diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 0a72894c052..6218a1672ed 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -1,12 +1,8 @@ -use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee}; +use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee}; use crate::{ beacon_state, - light_client_header::{ - LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, - }, test_utils::TestRandom, - BeaconBlock, BeaconState, ChainSpec, ForkName, ForkVersionDeserialize, LightClientHeader, - SignedBeaconBlock, + ForkName, ForkVersionDeserialize, LightClientHeader, }; use safe_arith::ArithError; use serde::{Deserialize, Deserializer, Serialize}; @@ -15,7 +11,6 @@ use ssz_derive::{Decode, Encode}; use ssz_types::typenum::{U4, U5, U6}; use std::sync::Arc; use test_random_derive::TestRandom; -use tree_hash::TreeHash; pub const FINALIZED_ROOT_INDEX: usize = 105; pub const CURRENT_SYNC_COMMITTEE_INDEX: usize = 54; @@ -96,75 +91,6 @@ pub struct LightClientUpdate { pub signature_slot: Slot, } -impl LightClientUpdate { - pub fn new( - chain_spec: ChainSpec, - beacon_state: BeaconState, - block: BeaconBlock, - attested_state: &mut BeaconState, - attested_block: &SignedBeaconBlock, - finalized_block: &SignedBeaconBlock, - ) -> Result { - let sync_aggregate = block.body().sync_aggregate()?; - if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { - return Err(Error::NotEnoughSyncCommitteeParticipants); - } - - let signature_period = block.epoch().sync_committee_period(&chain_spec)?; - // Compute and validate attested header. - let mut attested_header = attested_state.latest_block_header().clone(); - attested_header.state_root = attested_state.tree_hash_root(); - let attested_period = attested_header - .slot - .epoch(T::slots_per_epoch()) - .sync_committee_period(&chain_spec)?; - if attested_period != signature_period { - return Err(Error::MismatchingPeriods); - } - // Build finalized header from finalized block - let finalized_header = BeaconBlockHeader { - slot: finalized_block.slot(), - proposer_index: finalized_block.message().proposer_index(), - parent_root: finalized_block.parent_root(), - state_root: finalized_block.state_root(), - body_root: finalized_block.message().body_root(), - }; - if finalized_header.tree_hash_root() != beacon_state.finalized_checkpoint().root { - return Err(Error::InvalidFinalizedBlock); - } - let next_sync_committee_branch = - attested_state.compute_merkle_proof(NEXT_SYNC_COMMITTEE_INDEX)?; - let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; - - let (attested_header, finalized_header) = - match chain_spec.fork_name_at_epoch(beacon_state.slot().epoch(T::slots_per_epoch())) { - ForkName::Base => return Err(Error::AltairForkNotActive), - ForkName::Merge | ForkName::Altair => ( - LightClientHeaderAltair::block_to_light_client_header(attested_block)?.into(), - LightClientHeaderAltair::block_to_light_client_header(finalized_block)?.into(), - ), - ForkName::Capella => ( - LightClientHeaderCapella::block_to_light_client_header(attested_block)?.into(), - LightClientHeaderCapella::block_to_light_client_header(finalized_block)?.into(), - ), - ForkName::Deneb => ( - LightClientHeaderDeneb::block_to_light_client_header(attested_block)?.into(), - LightClientHeaderDeneb::block_to_light_client_header(finalized_block)?.into(), - ), - }; - - Ok(Self { - 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(), - }) - } -} - impl ForkVersionDeserialize for LightClientUpdate { fn deserialize_by_fork<'de, D: Deserializer<'de>>( value: Value, From b964f3fe1c2964685b573ac8ed17a5155d9f22ec Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Thu, 7 Mar 2024 20:37:04 +0200 Subject: [PATCH 60/72] add manual ssz decoding/encoding and add ssz_tests_by_fork macro --- .../src/rpc/codec/ssz_snappy.rs | 15 +++- .../lighthouse_network/src/types/pubsub.rs | 26 ++++-- consensus/types/src/light_client_bootstrap.rs | 59 +++++++------ .../types/src/light_client_finality_update.rs | 48 +++++++--- consensus/types/src/light_client_header.rs | 88 ++++++++++++++++++- .../src/light_client_optimistic_update.rs | 38 +++++--- consensus/types/src/light_client_update.rs | 55 ++++++++---- consensus/types/src/test_utils/macros.rs | 20 ++++- 8 files changed, 265 insertions(+), 84 deletions(-) diff --git a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs index 58c5dd7c8c6..c7752098604 100644 --- a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs +++ b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs @@ -590,9 +590,18 @@ fn handle_rpc_response( SupportedProtocol::MetaDataV1 => Ok(Some(RPCResponse::MetaData(MetaData::V1( MetaDataV1::from_ssz_bytes(decoded_buffer)?, )))), - SupportedProtocol::LightClientBootstrapV1 => Ok(Some(RPCResponse::LightClientBootstrap( - Arc::new(LightClientBootstrap::from_ssz_bytes(decoded_buffer)?), - ))), + SupportedProtocol::LightClientBootstrapV1 => match fork_name { + Some(fork_name) => Ok(Some(RPCResponse::LightClientBootstrap(Arc::new( + LightClientBootstrap::from_ssz_bytes(decoded_buffer, fork_name)?, + )))), + None => Err(RPCError::ErrorResponse( + RPCResponseErrorCode::InvalidRequest, + format!( + "No context bytes provided for {:?} response", + versioned_protocol + ), + )), + }, // MetaData V2 responses have no context bytes, so behave similarly to V1 responses SupportedProtocol::MetaDataV2 => Ok(Some(RPCResponse::MetaData(MetaData::V2( MetaDataV2::from_ssz_bytes(decoded_buffer)?, diff --git a/beacon_node/lighthouse_network/src/types/pubsub.rs b/beacon_node/lighthouse_network/src/types/pubsub.rs index 9bbc7b2650a..a4e2782f9c2 100644 --- a/beacon_node/lighthouse_network/src/types/pubsub.rs +++ b/beacon_node/lighthouse_network/src/types/pubsub.rs @@ -265,17 +265,31 @@ impl PubsubMessage { ))) } GossipKind::LightClientFinalityUpdate => { - let light_client_finality_update = - LightClientFinalityUpdate::from_ssz_bytes(data) - .map_err(|e| format!("{:?}", e))?; + let light_client_finality_update = match fork_context.from_context_bytes(gossip_topic.fork_digest) { + Some(&fork_name) => { + LightClientFinalityUpdate::from_ssz_bytes(data, fork_name) + .map_err(|e| format!("{:?}", e))? + }, + None => return Err(format!( + "light_client_finality_update topic invalid for given fork digest {:?}", + gossip_topic.fork_digest + )), + }; Ok(PubsubMessage::LightClientFinalityUpdate(Box::new( light_client_finality_update, ))) } GossipKind::LightClientOptimisticUpdate => { - let light_client_optimistic_update = - LightClientOptimisticUpdate::from_ssz_bytes(data) - .map_err(|e| format!("{:?}", e))?; + let light_client_optimistic_update = match fork_context.from_context_bytes(gossip_topic.fork_digest) { + Some(&fork_name) => { + LightClientOptimisticUpdate::from_ssz_bytes(data, fork_name) + .map_err(|e| format!("{:?}", e))? + }, + None => return Err(format!( + "light_client_optimistic_update topic invalid for given fork digest {:?}", + gossip_topic.fork_digest + )), + }; Ok(PubsubMessage::LightClientOptimisticUpdate(Box::new( light_client_optimistic_update, ))) diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 4b1995e8b10..205c69c0dcd 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -1,30 +1,19 @@ use super::{BeaconState, EthSpec, FixedVector, Hash256, SyncCommittee}; use crate::{ - light_client_header::{ - LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, - }, - light_client_update::*, - test_utils::TestRandom, - ChainSpec, ForkName, ForkVersionDeserialize, LightClientHeader, SignedBeaconBlock, + light_client_update::*, test_utils::TestRandom, ChainSpec, ForkName, ForkVersionDeserialize, + LightClientHeader, SignedBeaconBlock, }; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; -use ssz_derive::{Decode, Encode}; +use ssz::Decode; +use ssz_derive::Encode; use std::sync::Arc; use test_random_derive::TestRandom; /// A LightClientBootstrap is the initializer we send over to light_client nodes /// that are trying to generate their basic storage when booting up. #[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - arbitrary::Arbitrary, - TestRandom, + Debug, Clone, PartialEq, Serialize, Deserialize, Encode, arbitrary::Arbitrary, TestRandom, )] #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] @@ -48,18 +37,10 @@ impl LightClientBootstrap { let current_sync_committee_branch = beacon_state.compute_merkle_proof(CURRENT_SYNC_COMMITTEE_INDEX)?; - let header: LightClientHeader = match chain_spec - .fork_name_at_epoch(beacon_state.slot().epoch(T::slots_per_epoch())) - { - ForkName::Base => return Err(Error::AltairForkNotActive), - ForkName::Altair | ForkName::Merge => { - LightClientHeaderAltair::block_to_light_client_header(block)?.into() - } - ForkName::Capella => { - LightClientHeaderCapella::block_to_light_client_header(block)?.into() - } - ForkName::Deneb => LightClientHeaderDeneb::block_to_light_client_header(block)?.into(), - }; + let fork_name = + chain_spec.fork_name_at_epoch(beacon_state.slot().epoch(T::slots_per_epoch())); + + let header = LightClientHeader::::block_to_light_client_header(block, fork_name)?; Ok(LightClientBootstrap { header, @@ -67,6 +48,26 @@ impl LightClientBootstrap { current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, }) } + + pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result { + let mut builder = ssz::SszDecoderBuilder::new(bytes); + builder.register_anonymous_variable_length_item()?; + builder.register_type::>()?; + builder.register_type::>()?; + let mut decoder = builder.build()?; + + let header = decoder + .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; + let current_sync_committee = decoder.decode_next_with(SyncCommittee::from_ssz_bytes)?; + let current_sync_committee_branch = + decoder.decode_next_with(FixedVector::from_ssz_bytes)?; + + Ok(Self { + header, + current_sync_committee: Arc::new(current_sync_committee), + current_sync_committee_branch, + }) + } } impl ForkVersionDeserialize for LightClientBootstrap { @@ -92,5 +93,5 @@ mod tests { use super::*; use crate::MainnetEthSpec; - ssz_tests!(LightClientBootstrap); + ssz_tests_by_fork!(LightClientBootstrap, ForkName::Deneb); } diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index ac69986a825..f74deb3ba5b 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -1,26 +1,18 @@ use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate}; use crate::{ - light_client_update::*, - test_utils::TestRandom, - ForkName, ForkVersionDeserialize, LightClientHeader + light_client_update::*, test_utils::TestRandom, ForkName, ForkVersionDeserialize, + LightClientHeader, }; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; -use ssz_derive::{Decode, Encode}; +use ssz::Decode; +use ssz_derive::Encode; use test_random_derive::TestRandom; /// A LightClientFinalityUpdate is the update light_client request or received by a gossip that /// signal a new finalized beacon block header for the light client sync protocol. #[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - arbitrary::Arbitrary, - TestRandom, + Debug, Clone, PartialEq, Serialize, Deserialize, Encode, arbitrary::Arbitrary, TestRandom, )] #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] @@ -37,6 +29,34 @@ pub struct LightClientFinalityUpdate { pub signature_slot: Slot, } +impl LightClientFinalityUpdate { + pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result { + let mut builder = ssz::SszDecoderBuilder::new(bytes); + builder.register_anonymous_variable_length_item()?; + builder.register_anonymous_variable_length_item()?; + builder.register_type::>()?; + builder.register_type::>()?; + builder.register_type::()?; + let mut decoder = builder.build()?; + + let attested_header = decoder + .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; + let finalized_header = decoder + .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; + let finality_branch = decoder.decode_next_with(FixedVector::from_ssz_bytes)?; + let sync_aggregate = decoder.decode_next_with(SyncAggregate::from_ssz_bytes)?; + let signature_slot = decoder.decode_next_with(Slot::from_ssz_bytes)?; + + Ok(Self { + attested_header, + finalized_header, + finality_branch, + sync_aggregate, + signature_slot, + }) + } +} + impl ForkVersionDeserialize for LightClientFinalityUpdate { fn deserialize_by_fork<'de, D: Deserializer<'de>>( value: Value, @@ -60,5 +80,5 @@ mod tests { use super::*; use crate::MainnetEthSpec; - ssz_tests!(LightClientFinalityUpdate); + ssz_tests_by_fork!(LightClientFinalityUpdate, ForkName::Deneb); } diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 58554904768..84d57254b92 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -8,6 +8,7 @@ use crate::{ }; use serde::{Deserialize, Serialize}; use serde_json; +use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use std::marker::PhantomData; use superstruct::superstruct; @@ -22,8 +23,8 @@ use test_random_derive::TestRandom; PartialEq, Serialize, Deserialize, - Encode, Decode, + Encode, TestRandom, arbitrary::Arbitrary, ), @@ -31,10 +32,10 @@ use test_random_derive::TestRandom; arbitrary(bound = "E: EthSpec"), ) )] -#[derive(Debug, Clone, Serialize, Deserialize, Decode, Encode, arbitrary::Arbitrary, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, arbitrary::Arbitrary, PartialEq)] #[serde(untagged)] +// #[ssz(enum_behaviour = "transparent")] #[serde(bound = "E: EthSpec", deny_unknown_fields)] -#[ssz(enum_behaviour = "union")] #[arbitrary(bound = "E: EthSpec")] pub struct LightClientHeader { pub beacon: BeaconBlockHeader, @@ -53,6 +54,85 @@ pub struct LightClientHeader { pub phantom_data: PhantomData, } +impl LightClientHeader { + pub fn block_to_light_client_header( + block: &SignedBeaconBlock, + fork_name: ForkName, + ) -> Result { + let header = match fork_name { + ForkName::Base => return Err(Error::AltairForkNotActive), + ForkName::Altair | ForkName::Merge => LightClientHeader::Altair( + LightClientHeaderAltair::block_to_light_client_header(block)?, + ), + ForkName::Capella => LightClientHeader::Capella( + LightClientHeaderCapella::block_to_light_client_header(block)?, + ), + ForkName::Deneb => LightClientHeader::Deneb( + LightClientHeaderDeneb::block_to_light_client_header(block)?, + ), + }; + Ok(header) + } + + pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result { + let header = match fork_name { + ForkName::Altair | ForkName::Merge => { + let header = LightClientHeaderAltair::from_ssz_bytes(bytes)?; + LightClientHeader::Altair(header) + } + ForkName::Capella => { + let header = LightClientHeaderCapella::from_ssz_bytes(bytes)?; + LightClientHeader::Capella(header) + } + ForkName::Deneb => { + let header = LightClientHeaderDeneb::from_ssz_bytes(bytes)?; + LightClientHeader::Deneb(header) + } + ForkName::Base => { + return Err(ssz::DecodeError::BytesInvalid(format!( + "LightClientHeader decoding for {fork_name} not implemented" + ))) + } + }; + + Ok(header) + } +} + +impl Encode for LightClientHeader { + fn is_ssz_fixed_len() -> bool { + false + } + + fn ssz_bytes_len(&self) -> usize { + match self { + LightClientHeader::Altair(header) => header.ssz_bytes_len(), + LightClientHeader::Capella(header) => header.ssz_bytes_len(), + LightClientHeader::Deneb(header) => header.ssz_bytes_len(), + } + } + + fn ssz_fixed_len() -> usize { + ssz::BYTES_PER_LENGTH_OFFSET + } + + fn as_ssz_bytes(&self) -> Vec { + match self { + LightClientHeader::Altair(header) => header.as_ssz_bytes(), + LightClientHeader::Capella(header) => header.as_ssz_bytes(), + LightClientHeader::Deneb(header) => header.as_ssz_bytes(), + } + } + + fn ssz_append(&self, buf: &mut Vec) { + match self { + LightClientHeader::Altair(header) => header.ssz_append(buf), + LightClientHeader::Capella(header) => header.ssz_append(buf), + LightClientHeader::Deneb(header) => header.ssz_append(buf), + } + } +} + impl LightClientHeaderAltair { pub fn block_to_light_client_header(block: &SignedBeaconBlock) -> Result { Ok(LightClientHeaderAltair { @@ -135,7 +215,7 @@ impl ForkVersionDeserialize for LightClientHeader { ForkName::Deneb => serde_json::from_value(value) .map(|light_client_header| Self::Deneb(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" ))), } diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index 39be0d0f1b0..935a97aabe2 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -1,23 +1,16 @@ use super::{EthSpec, ForkName, ForkVersionDeserialize, Slot, SyncAggregate}; use crate::test_utils::TestRandom; -use crate::{light_client_header::LightClientHeader}; +use crate::LightClientHeader; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; -use ssz_derive::{Decode, Encode}; +use ssz::Decode; +use ssz_derive::Encode; use test_random_derive::TestRandom; /// A LightClientOptimisticUpdate is the update we send on each slot, /// it is based off the current unfinalized epoch is verified only against BLS signature. #[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - arbitrary::Arbitrary, - TestRandom, + Debug, Clone, PartialEq, Serialize, Deserialize, Encode, arbitrary::Arbitrary, TestRandom, )] #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] @@ -30,6 +23,27 @@ pub struct LightClientOptimisticUpdate { pub signature_slot: Slot, } +impl LightClientOptimisticUpdate { + pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result { + let mut builder = ssz::SszDecoderBuilder::new(bytes); + builder.register_anonymous_variable_length_item()?; + builder.register_type::>()?; + builder.register_type::()?; + let mut decoder = builder.build()?; + + let attested_header = decoder + .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; + let sync_aggregate = decoder.decode_next_with(SyncAggregate::from_ssz_bytes)?; + let signature_slot = decoder.decode_next_with(Slot::from_ssz_bytes)?; + + Ok(Self { + attested_header, + sync_aggregate, + signature_slot, + }) + } +} + impl ForkVersionDeserialize for LightClientOptimisticUpdate { fn deserialize_by_fork<'de, D: Deserializer<'de>>( value: Value, @@ -53,5 +67,5 @@ mod tests { use super::*; use crate::MainnetEthSpec; - ssz_tests!(LightClientOptimisticUpdate); + ssz_tests_by_fork!(LightClientOptimisticUpdate, ForkName::Deneb); } diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 6218a1672ed..0de5f54631f 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -1,13 +1,12 @@ use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee}; use crate::{ - beacon_state, - test_utils::TestRandom, - ForkName, ForkVersionDeserialize, LightClientHeader, + beacon_state, test_utils::TestRandom, ForkName, ForkVersionDeserialize, LightClientHeader, }; use safe_arith::ArithError; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; -use ssz_derive::{Decode, Encode}; +use ssz::Decode; +use ssz_derive::Encode; use ssz_types::typenum::{U4, U5, U6}; use std::sync::Arc; use test_random_derive::TestRandom; @@ -58,19 +57,11 @@ impl From for Error { } } -/// A LightClientUpdate is the update we request solely to either complete the bootstraping process, +/// A LightClientUpdate is the update we request solely to either complete the bootstrapping process, /// 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]. #[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Decode, - arbitrary::Arbitrary, - TestRandom, + Debug, Clone, PartialEq, Serialize, Deserialize, Encode, arbitrary::Arbitrary, TestRandom, )] #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] @@ -109,13 +100,47 @@ impl ForkVersionDeserialize for LightClientUpdate { } } +impl LightClientUpdate { + pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result { + let mut builder = ssz::SszDecoderBuilder::new(bytes); + builder.register_anonymous_variable_length_item()?; + builder.register_type::>()?; + builder.register_type::>()?; + builder.register_anonymous_variable_length_item()?; + builder.register_type::>()?; + builder.register_type::>()?; + builder.register_type::()?; + let mut decoder = builder.build()?; + + let attested_header = decoder + .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; + let next_sync_committee = decoder.decode_next_with(SyncCommittee::from_ssz_bytes)?; + let next_sync_committee_branch = decoder.decode_next_with(FixedVector::from_ssz_bytes)?; + let finalized_header = decoder + .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; + let finality_branch = decoder.decode_next_with(FixedVector::from_ssz_bytes)?; + let sync_aggregate = decoder.decode_next_with(SyncAggregate::from_ssz_bytes)?; + let signature_slot = decoder.decode_next_with(Slot::from_ssz_bytes)?; + + Ok(Self { + attested_header, + next_sync_committee: Arc::new(next_sync_committee), + next_sync_committee_branch, + finalized_header, + finality_branch, + sync_aggregate, + signature_slot, + }) + } +} + #[cfg(test)] mod tests { use super::*; use crate::MainnetEthSpec; use ssz_types::typenum::Unsigned; - ssz_tests!(LightClientUpdate); + ssz_tests_by_fork!(LightClientUpdate, ForkName::Deneb); #[test] fn finalized_root_params() { diff --git a/consensus/types/src/test_utils/macros.rs b/consensus/types/src/test_utils/macros.rs index 1e275a5760e..c4615434aa2 100644 --- a/consensus/types/src/test_utils/macros.rs +++ b/consensus/types/src/test_utils/macros.rs @@ -20,7 +20,6 @@ macro_rules! ssz_tests { let original = <$type>::random_for_test(&mut rng); let bytes = ssz_encode(&original); - println!("bytes length: {}", bytes.len()); let decoded = <$type>::from_ssz_bytes(&bytes).unwrap(); assert_eq!(original, decoded); @@ -28,6 +27,25 @@ macro_rules! ssz_tests { }; } +#[macro_export] +macro_rules! ssz_tests_by_fork { + ($type: ty, $fork:expr) => { + #[test] + pub fn test_ssz_round_trip() { + use ssz::ssz_encode; + use $crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; + + let mut rng = XorShiftRng::from_seed([42; 16]); + let original = <$type>::random_for_test(&mut rng); + + let bytes = ssz_encode(&original); + let decoded = <$type>::from_ssz_bytes(&bytes, $fork).unwrap(); + + assert_eq!(original, decoded); + } + }; +} + #[macro_export] macro_rules! tree_hash_tests { ($type: ty) => { From 3a3616c18b331096744e30e568ae92a0aa48aa40 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Thu, 7 Mar 2024 22:13:08 +0200 Subject: [PATCH 61/72] merge ssz tests, revert code deletion, cleanup --- consensus/types/src/lib.rs | 4 +- consensus/types/src/light_client_bootstrap.rs | 20 +++--- .../types/src/light_client_finality_update.rs | 4 +- consensus/types/src/light_client_header.rs | 1 - consensus/types/src/light_client_update.rs | 67 ++++++++++++++++++- testing/ef_tests/src/type_name.rs | 5 +- 6 files changed, 83 insertions(+), 18 deletions(-) diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index b07b497a2ae..dad7ab9f999 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -154,7 +154,9 @@ pub use crate::historical_batch::HistoricalBatch; pub use crate::indexed_attestation::IndexedAttestation; pub use crate::light_client_bootstrap::LightClientBootstrap; pub use crate::light_client_finality_update::LightClientFinalityUpdate; -pub use crate::light_client_header::LightClientHeader; +pub use crate::light_client_header::{ + LightClientHeader, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, +}; pub use crate::light_client_optimistic_update::LightClientOptimisticUpdate; pub use crate::light_client_update::{Error as LightClientError, LightClientUpdate}; pub use crate::participation_flags::ParticipationFlags; diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 349603062a6..a02898c59ec 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -9,7 +9,6 @@ use ssz::Decode; use ssz_derive::Encode; use std::sync::Arc; use test_random_derive::TestRandom; -use tree_hash_derive::TreeHash; /// A LightClientBootstrap is the initializer we send over to light_client nodes /// that are trying to generate their basic storage when booting up. @@ -27,10 +26,10 @@ pub struct LightClientBootstrap { pub current_sync_committee_branch: FixedVector, } -impl LightClientBootstrap { +impl LightClientBootstrap { pub fn from_beacon_state( - beacon_state: &mut BeaconState, - block: &SignedBeaconBlock, + beacon_state: &mut BeaconState, + block: &SignedBeaconBlock, chain_spec: &ChainSpec, ) -> Result { let mut header = beacon_state.latest_block_header().clone(); @@ -38,10 +37,11 @@ impl LightClientBootstrap { let current_sync_committee_branch = beacon_state.compute_merkle_proof(CURRENT_SYNC_COMMITTEE_INDEX)?; - let fork_name = - chain_spec.fork_name_at_epoch(beacon_state.slot().epoch(T::slots_per_epoch())); + let fork_name = beacon_state + .fork_name(chain_spec) + .map_err(|_| Error::InconsistentFork)?; - let header = LightClientHeader::::block_to_light_client_header(block, fork_name)?; + let header = LightClientHeader::::block_to_light_client_header(block, fork_name)?; Ok(LightClientBootstrap { header, @@ -53,7 +53,7 @@ impl LightClientBootstrap { pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result { let mut builder = ssz::SszDecoderBuilder::new(bytes); builder.register_anonymous_variable_length_item()?; - builder.register_type::>()?; + builder.register_type::>()?; builder.register_type::>()?; let mut decoder = builder.build()?; @@ -71,14 +71,14 @@ impl LightClientBootstrap { } } -impl ForkVersionDeserialize for LightClientBootstrap { +impl ForkVersionDeserialize for LightClientBootstrap { fn deserialize_by_fork<'de, D: Deserializer<'de>>( value: Value, fork_name: ForkName, ) -> Result { match fork_name { ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => { - Ok(serde_json::from_value::>(value) + Ok(serde_json::from_value::>(value) .map_err(serde::de::Error::custom))? } ForkName::Base => Err(serde::de::Error::custom(format!( diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index f74deb3ba5b..91febfc5957 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -57,14 +57,14 @@ impl LightClientFinalityUpdate { } } -impl ForkVersionDeserialize for LightClientFinalityUpdate { +impl ForkVersionDeserialize for LightClientFinalityUpdate { fn deserialize_by_fork<'de, D: Deserializer<'de>>( value: Value, fork_name: ForkName, ) -> Result { match fork_name { ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => Ok( - serde_json::from_value::>(value) + serde_json::from_value::>(value) .map_err(serde::de::Error::custom), )?, ForkName::Base => Err(serde::de::Error::custom(format!( diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 03de79db882..84d57254b92 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -13,7 +13,6 @@ use ssz_derive::{Decode, Encode}; use std::marker::PhantomData; use superstruct::superstruct; use test_random_derive::TestRandom; -use tree_hash_derive::TreeHash; #[superstruct( variants(Altair, Capella, Deneb), diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 0de5f54631f..55915f7430c 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -1,6 +1,7 @@ use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee}; use crate::{ - beacon_state, test_utils::TestRandom, ForkName, ForkVersionDeserialize, LightClientHeader, + beacon_state, test_utils::TestRandom, BeaconBlock, BeaconBlockHeader, BeaconState, ChainSpec, + ForkName, ForkVersionDeserialize, LightClientHeader, SignedBeaconBlock, }; use safe_arith::ArithError; use serde::{Deserialize, Deserializer, Serialize}; @@ -10,6 +11,7 @@ use ssz_derive::Encode; use ssz_types::typenum::{U4, U5, U6}; use std::sync::Arc; use test_random_derive::TestRandom; +use tree_hash::TreeHash; pub const FINALIZED_ROOT_INDEX: usize = 105; pub const CURRENT_SYNC_COMMITTEE_INDEX: usize = 54; @@ -37,6 +39,7 @@ pub enum Error { MismatchingPeriods, InvalidFinalizedBlock, BeaconBlockBodyError, + InconsistentFork, } impl From for Error { @@ -82,14 +85,14 @@ pub struct LightClientUpdate { pub signature_slot: Slot, } -impl ForkVersionDeserialize for LightClientUpdate { +impl ForkVersionDeserialize for LightClientUpdate { fn deserialize_by_fork<'de, D: Deserializer<'de>>( value: Value, fork_name: ForkName, ) -> Result { match fork_name { ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => { - Ok(serde_json::from_value::>(value) + Ok(serde_json::from_value::>(value) .map_err(serde::de::Error::custom))? } ForkName::Base => Err(serde::de::Error::custom(format!( @@ -101,6 +104,64 @@ impl ForkVersionDeserialize for LightClientUpdate { } impl LightClientUpdate { + pub fn new( + chain_spec: ChainSpec, + beacon_state: BeaconState, + block: BeaconBlock, + attested_state: &mut BeaconState, + attested_block: &SignedBeaconBlock, + finalized_block: &SignedBeaconBlock, + ) -> Result { + let sync_aggregate = block.body().sync_aggregate()?; + if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { + return Err(Error::NotEnoughSyncCommitteeParticipants); + } + + let signature_period = block.epoch().sync_committee_period(&chain_spec)?; + // Compute and validate attested header. + let mut attested_header = attested_state.latest_block_header().clone(); + attested_header.state_root = attested_state.tree_hash_root(); + let attested_period = attested_header + .slot + .epoch(E::slots_per_epoch()) + .sync_committee_period(&chain_spec)?; + if attested_period != signature_period { + return Err(Error::MismatchingPeriods); + } + // Build finalized header from finalized block + let finalized_header = BeaconBlockHeader { + slot: finalized_block.slot(), + proposer_index: finalized_block.message().proposer_index(), + parent_root: finalized_block.parent_root(), + state_root: finalized_block.state_root(), + body_root: finalized_block.message().body_root(), + }; + if finalized_header.tree_hash_root() != beacon_state.finalized_checkpoint().root { + return Err(Error::InvalidFinalizedBlock); + } + let next_sync_committee_branch = + attested_state.compute_merkle_proof(NEXT_SYNC_COMMITTEE_INDEX)?; + let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; + let fork_name = beacon_state + .fork_name(&chain_spec) + .map_err(|_| Error::InconsistentFork)?; + + let attested_header = + LightClientHeader::block_to_light_client_header(attested_block, fork_name)?; + let finalized_header = + LightClientHeader::block_to_light_client_header(finalized_block, fork_name)?; + + Ok(Self { + 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(), + }) + } + pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result { let mut builder = ssz::SszDecoderBuilder::new(bytes); builder.register_anonymous_variable_length_item()?; diff --git a/testing/ef_tests/src/type_name.rs b/testing/ef_tests/src/type_name.rs index 9c56db8f286..62bba571d76 100644 --- a/testing/ef_tests/src/type_name.rs +++ b/testing/ef_tests/src/type_name.rs @@ -75,7 +75,10 @@ type_name_generic!(HistoricalBatch); type_name_generic!(IndexedAttestation); type_name_generic!(LightClientBootstrap); type_name_generic!(LightClientFinalityUpdate); -type_name!(LightClientHeader); +type_name_generic!(LightClientHeader); +type_name_generic!(LightClientHeaderDeneb, "LightClientHeader"); +type_name_generic!(LightClientHeaderCapella, "LightClientHeader"); +type_name_generic!(LightClientHeaderAltair, "LightClientHeader"); type_name_generic!(LightClientOptimisticUpdate); type_name_generic!(LightClientUpdate); type_name_generic!(PendingAttestation); From 6ddfe7e11ef3355af2dae80d91bba18278e6cc11 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Thu, 7 Mar 2024 22:34:19 +0200 Subject: [PATCH 62/72] move chainspec --- consensus/types/src/light_client_update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 55915f7430c..51e254b36e5 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -105,12 +105,12 @@ impl ForkVersionDeserialize for LightClientUpdate { impl LightClientUpdate { pub fn new( - chain_spec: ChainSpec, beacon_state: BeaconState, block: BeaconBlock, attested_state: &mut BeaconState, attested_block: &SignedBeaconBlock, finalized_block: &SignedBeaconBlock, + chain_spec: ChainSpec, ) -> Result { let sync_aggregate = block.body().sync_aggregate()?; if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { From 258f51bbb924a31f9c178baacd3ba41059c518ed Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Fri, 8 Mar 2024 16:16:20 +0200 Subject: [PATCH 63/72] update ssz tests --- consensus/types/src/light_client_bootstrap.rs | 24 +++--- .../types/src/light_client_finality_update.rs | 13 ++- consensus/types/src/light_client_header.rs | 35 +++++--- .../src/light_client_optimistic_update.rs | 11 ++- consensus/types/src/light_client_update.rs | 30 ++++--- testing/ef_tests/tests/tests.rs | 79 ++++++++++--------- 6 files changed, 117 insertions(+), 75 deletions(-) diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index a02898c59ec..449d6e20db3 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -5,15 +5,16 @@ use crate::{ }; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; -use ssz::Decode; use ssz_derive::Encode; use std::sync::Arc; use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; +use derivative::Derivative; /// A LightClientBootstrap is the initializer we send over to light_client nodes /// that are trying to generate their basic storage when booting up. #[derive( - Debug, Clone, PartialEq, Serialize, Deserialize, Encode, arbitrary::Arbitrary, TestRandom, + Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Derivative, TreeHash, arbitrary::Arbitrary, TestRandom, )] #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] @@ -37,11 +38,7 @@ impl LightClientBootstrap { let current_sync_committee_branch = beacon_state.compute_merkle_proof(CURRENT_SYNC_COMMITTEE_INDEX)?; - let fork_name = beacon_state - .fork_name(chain_spec) - .map_err(|_| Error::InconsistentFork)?; - - let header = LightClientHeader::::block_to_light_client_header(block, fork_name)?; + let header = LightClientHeader::::block_to_light_client_header(block, chain_spec)?; Ok(LightClientBootstrap { header, @@ -56,12 +53,10 @@ impl LightClientBootstrap { builder.register_type::>()?; builder.register_type::>()?; let mut decoder = builder.build()?; - let header = decoder .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; - let current_sync_committee = decoder.decode_next_with(SyncCommittee::from_ssz_bytes)?; - let current_sync_committee_branch = - decoder.decode_next_with(FixedVector::from_ssz_bytes)?; + let current_sync_committee = decoder.decode_next()?; + let current_sync_committee_branch = decoder.decode_next()?; Ok(Self { header, @@ -69,6 +64,13 @@ impl LightClientBootstrap { current_sync_committee_branch, }) } + + pub fn from_ssz_bytes_for_fork( + bytes: &[u8], + fork_name: ForkName, + ) -> Result { + Self::from_ssz_bytes(bytes, fork_name) + } } impl ForkVersionDeserialize for LightClientBootstrap { diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index 91febfc5957..f6a9c37925a 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -43,9 +43,9 @@ impl LightClientFinalityUpdate { .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; let finalized_header = decoder .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; - let finality_branch = decoder.decode_next_with(FixedVector::from_ssz_bytes)?; - let sync_aggregate = decoder.decode_next_with(SyncAggregate::from_ssz_bytes)?; - let signature_slot = decoder.decode_next_with(Slot::from_ssz_bytes)?; + let finality_branch = decoder.decode_next()?; + let sync_aggregate = decoder.decode_next()?; + let signature_slot = decoder.decode_next()?; Ok(Self { attested_header, @@ -55,6 +55,13 @@ impl LightClientFinalityUpdate { signature_slot, }) } + + pub fn from_ssz_bytes_for_fork( + bytes: &[u8], + fork_name: ForkName, + ) -> Result { + Self::from_ssz_bytes(bytes, fork_name) + } } impl ForkVersionDeserialize for LightClientFinalityUpdate { diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 84d57254b92..163f1bf9c72 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,10 +1,11 @@ use crate::BeaconBlockHeader; +use crate::ChainSpec; use crate::ForkName; use crate::ForkVersionDeserialize; use crate::{light_client_update::*, BeaconBlockBody}; use crate::{ test_utils::TestRandom, EthSpec, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, - FixedVector, Hash256, SignedBeaconBlock, + FixedVector, Hash256, SignedBeaconBlock }; use serde::{Deserialize, Serialize}; use serde_json; @@ -13,6 +14,8 @@ use ssz_derive::{Decode, Encode}; use std::marker::PhantomData; use superstruct::superstruct; use test_random_derive::TestRandom; +use derivative::Derivative; +use tree_hash_derive::TreeHash; #[superstruct( variants(Altair, Capella, Deneb), @@ -23,18 +26,20 @@ use test_random_derive::TestRandom; PartialEq, Serialize, Deserialize, + Derivative, Decode, Encode, TestRandom, arbitrary::Arbitrary, + TreeHash, ), serde(bound = "E: EthSpec", deny_unknown_fields), arbitrary(bound = "E: EthSpec"), ) )] -#[derive(Debug, Clone, Serialize, Deserialize, arbitrary::Arbitrary, PartialEq)] +#[derive(Debug, Clone, Serialize, TreeHash, Deserialize, arbitrary::Arbitrary, PartialEq)] #[serde(untagged)] -// #[ssz(enum_behaviour = "transparent")] +#[tree_hash(enum_behaviour = "transparent")] #[serde(bound = "E: EthSpec", deny_unknown_fields)] #[arbitrary(bound = "E: EthSpec")] pub struct LightClientHeader { @@ -49,17 +54,19 @@ pub struct LightClientHeader { #[superstruct(only(Deneb), partial_getter(rename = "execution_payload_header_deneb"))] pub execution: ExecutionPayloadHeaderDeneb, - #[serde(skip)] #[ssz(skip_serializing, skip_deserializing)] - pub phantom_data: PhantomData, + #[tree_hash(skip_hashing)] + #[serde(skip)] + #[arbitrary(default)] + pub _phantom_data: PhantomData, } impl LightClientHeader { pub fn block_to_light_client_header( block: &SignedBeaconBlock, - fork_name: ForkName, + chain_spec: &ChainSpec, ) -> Result { - let header = match fork_name { + let header = match block.fork_name(chain_spec).map_err(|_| Error::InconsistentFork)? { ForkName::Base => return Err(Error::AltairForkNotActive), ForkName::Altair | ForkName::Merge => LightClientHeader::Altair( LightClientHeaderAltair::block_to_light_client_header(block)?, @@ -97,6 +104,14 @@ impl LightClientHeader { Ok(header) } + + /// Custom SSZ decoder that takes a `ForkName` as context. + pub fn from_ssz_bytes_for_fork( + bytes: &[u8], + fork_name: ForkName, + ) -> Result { + Self::from_ssz_bytes(bytes, fork_name) + } } impl Encode for LightClientHeader { @@ -137,7 +152,7 @@ impl LightClientHeaderAltair { pub fn block_to_light_client_header(block: &SignedBeaconBlock) -> Result { Ok(LightClientHeaderAltair { beacon: block.message().block_header(), - phantom_data: PhantomData, + _phantom_data: PhantomData, }) } } @@ -166,7 +181,7 @@ impl LightClientHeaderCapella { beacon: block.message().block_header(), execution: header, execution_branch: FixedVector::new(execution_branch)?, - phantom_data: PhantomData, + _phantom_data: PhantomData, }); } } @@ -195,7 +210,7 @@ impl LightClientHeaderDeneb { beacon: block.message().block_header(), execution: header, execution_branch: FixedVector::new(execution_branch)?, - phantom_data: PhantomData, + _phantom_data: PhantomData, }) } } diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index 935a97aabe2..4de87989a15 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -33,8 +33,8 @@ impl LightClientOptimisticUpdate { let attested_header = decoder .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; - let sync_aggregate = decoder.decode_next_with(SyncAggregate::from_ssz_bytes)?; - let signature_slot = decoder.decode_next_with(Slot::from_ssz_bytes)?; + let sync_aggregate = decoder.decode_next()?; + let signature_slot = decoder.decode_next()?; Ok(Self { attested_header, @@ -42,6 +42,13 @@ impl LightClientOptimisticUpdate { signature_slot, }) } + + pub fn from_ssz_bytes_for_fork( + bytes: &[u8], + fork_name: ForkName, + ) -> Result { + Self::from_ssz_bytes(bytes, fork_name) + } } impl ForkVersionDeserialize for LightClientOptimisticUpdate { diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 51e254b36e5..a7704d1728b 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -110,21 +110,21 @@ impl LightClientUpdate { attested_state: &mut BeaconState, attested_block: &SignedBeaconBlock, finalized_block: &SignedBeaconBlock, - chain_spec: ChainSpec, + chain_spec: &ChainSpec, ) -> Result { let sync_aggregate = block.body().sync_aggregate()?; if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { return Err(Error::NotEnoughSyncCommitteeParticipants); } - let signature_period = block.epoch().sync_committee_period(&chain_spec)?; + let signature_period = block.epoch().sync_committee_period(chain_spec)?; // Compute and validate attested header. let mut attested_header = attested_state.latest_block_header().clone(); attested_header.state_root = attested_state.tree_hash_root(); let attested_period = attested_header .slot .epoch(E::slots_per_epoch()) - .sync_committee_period(&chain_spec)?; + .sync_committee_period(chain_spec)?; if attested_period != signature_period { return Err(Error::MismatchingPeriods); } @@ -142,14 +142,11 @@ impl LightClientUpdate { let next_sync_committee_branch = attested_state.compute_merkle_proof(NEXT_SYNC_COMMITTEE_INDEX)?; let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; - let fork_name = beacon_state - .fork_name(&chain_spec) - .map_err(|_| Error::InconsistentFork)?; let attested_header = - LightClientHeader::block_to_light_client_header(attested_block, fork_name)?; + LightClientHeader::block_to_light_client_header(attested_block, chain_spec)?; let finalized_header = - LightClientHeader::block_to_light_client_header(finalized_block, fork_name)?; + LightClientHeader::block_to_light_client_header(finalized_block, chain_spec)?; Ok(Self { attested_header, @@ -175,13 +172,13 @@ impl LightClientUpdate { let attested_header = decoder .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; - let next_sync_committee = decoder.decode_next_with(SyncCommittee::from_ssz_bytes)?; - let next_sync_committee_branch = decoder.decode_next_with(FixedVector::from_ssz_bytes)?; + let next_sync_committee = decoder.decode_next()?; + let next_sync_committee_branch = decoder.decode_next()?; let finalized_header = decoder .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; - let finality_branch = decoder.decode_next_with(FixedVector::from_ssz_bytes)?; - let sync_aggregate = decoder.decode_next_with(SyncAggregate::from_ssz_bytes)?; - let signature_slot = decoder.decode_next_with(Slot::from_ssz_bytes)?; + let finality_branch = decoder.decode_next()?; + let sync_aggregate = decoder.decode_next()?; + let signature_slot = decoder.decode_next()?; Ok(Self { attested_header, @@ -193,6 +190,13 @@ impl LightClientUpdate { signature_slot, }) } + + pub fn from_ssz_bytes_for_fork( + bytes: &[u8], + fork_name: ForkName, + ) -> Result { + Self::from_ssz_bytes(bytes, fork_name) + } } #[cfg(test)] diff --git a/testing/ef_tests/tests/tests.rs b/testing/ef_tests/tests/tests.rs index f2c2ffe31f7..9168d8178e9 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -217,7 +217,7 @@ mod ssz_static { use ef_tests::{Handler, SszStaticHandler, SszStaticTHCHandler, SszStaticWithSpecHandler}; use types::blob_sidecar::BlobIdentifier; use types::historical_summary::HistoricalSummary; - use types::*; + use types::{LightClientHeaderAltair, *}; ssz_static_test!(aggregate_and_proof, AggregateAndProof<_>); ssz_static_test!(attestation, Attestation<_>); @@ -249,6 +249,7 @@ mod ssz_static { ssz_static_test!(signing_data, SigningData); ssz_static_test!(validator, Validator); ssz_static_test!(voluntary_exit, VoluntaryExit); + // ssz_static_test!(light_client_bootstrap, SszStaticWithSpecHandler, LightClientBootstrap<_>); // BeaconBlockBody has no internal indicator of which fork it is for, so we test it separately. #[test] @@ -276,54 +277,60 @@ mod ssz_static { // Altair and later #[test] fn contribution_and_proof() { - SszStaticHandler::, MinimalEthSpec>::altair_and_later( + SszStaticHandler::, MinimalEthSpec>::altair_only( ) .run(); - SszStaticHandler::, MainnetEthSpec>::altair_and_later( + SszStaticHandler::, MainnetEthSpec>::altair_only( ) .run(); } - #[test] - fn light_client_bootstrap() { - SszStaticHandler::, MinimalEthSpec>::altair_only() - .run(); - SszStaticHandler::, MainnetEthSpec>::altair_only() - .run(); - } - - #[test] - fn light_client_finality_update() { - SszStaticHandler::, MinimalEthSpec>::altair_only( - ) - .run(); - SszStaticHandler::, MainnetEthSpec>::altair_only( - ) - .run(); - } + // #[test] + // fn light_client_bootstrap() { + // SszStaticHandler::, MinimalEthSpec>::altair_and_later() + // .run(); + // SszStaticHandler::, MainnetEthSpec>::altair_and_later() + // .run(); + // } + + // #[test] + // fn light_client_finality_update() { + // SszStaticHandler::, MinimalEthSpec>::altair_and_later( + // ) + // .run(); + // SszStaticHandler::, MainnetEthSpec>::altair_and_later( + // ) + // .run(); + // } #[test] fn light_client_header() { - SszStaticHandler::::altair_only().run(); - SszStaticHandler::::altair_only().run(); - } + SszStaticHandler::, MinimalEthSpec>::altair_only().run(); + SszStaticHandler::, MainnetEthSpec>::altair_only().run(); - #[test] - fn light_client_optimistic_update() { - SszStaticHandler::, MinimalEthSpec>::altair_only( - ) - .run(); - SszStaticHandler::, MainnetEthSpec>::altair_only( - ) - .run(); - } + SszStaticHandler::, MinimalEthSpec>::capella_only().run(); + SszStaticHandler::, MainnetEthSpec>::capella_only().run(); - #[test] - fn light_client_update() { - SszStaticHandler::, MinimalEthSpec>::altair_only().run(); - SszStaticHandler::, MainnetEthSpec>::altair_only().run(); + SszStaticHandler::, MinimalEthSpec>::deneb_only().run(); + SszStaticHandler::, MainnetEthSpec>::deneb_only().run(); } + // #[test] + // fn light_client_optimistic_update() { + // SszStaticHandler::, MinimalEthSpec>::altair_and_later( + // ) + // .run(); + // SszStaticHandler::, MainnetEthSpec>::altair_and_later( + // ) + // .run(); + // } + + // #[test] + // fn light_client_update() { + // SszStaticHandler::, MinimalEthSpec>::altair_and_later().run(); + // SszStaticHandler::, MainnetEthSpec>::altair_and_later().run(); + // } + #[test] fn signed_contribution_and_proof() { SszStaticHandler::, MinimalEthSpec>::altair_and_later().run(); From cb41899b6cde714f316cf45d71520a36473e748f Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Fri, 8 Mar 2024 16:26:09 +0200 Subject: [PATCH 64/72] fmt --- consensus/types/src/light_client_bootstrap.rs | 13 ++++++-- .../types/src/light_client_finality_update.rs | 1 - consensus/types/src/light_client_header.rs | 9 ++++-- .../src/light_client_optimistic_update.rs | 1 - consensus/types/src/light_client_update.rs | 1 - testing/ef_tests/tests/tests.rs | 30 +++++++++++-------- 6 files changed, 35 insertions(+), 20 deletions(-) diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 449d6e20db3..7f3340cb992 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -3,18 +3,27 @@ use crate::{ light_client_update::*, test_utils::TestRandom, ChainSpec, ForkName, ForkVersionDeserialize, LightClientHeader, SignedBeaconBlock, }; +use derivative::Derivative; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use ssz_derive::Encode; use std::sync::Arc; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; -use derivative::Derivative; /// A LightClientBootstrap is the initializer we send over to light_client nodes /// that are trying to generate their basic storage when booting up. #[derive( - Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Derivative, TreeHash, arbitrary::Arbitrary, TestRandom, + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Derivative, + TreeHash, + arbitrary::Arbitrary, + TestRandom, )] #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index f6a9c37925a..10bb0b24946 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -5,7 +5,6 @@ use crate::{ }; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; -use ssz::Decode; use ssz_derive::Encode; use test_random_derive::TestRandom; diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 163f1bf9c72..70a62369a34 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -5,8 +5,9 @@ use crate::ForkVersionDeserialize; use crate::{light_client_update::*, BeaconBlockBody}; use crate::{ test_utils::TestRandom, EthSpec, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, - FixedVector, Hash256, SignedBeaconBlock + FixedVector, Hash256, SignedBeaconBlock, }; +use derivative::Derivative; use serde::{Deserialize, Serialize}; use serde_json; use ssz::{Decode, Encode}; @@ -14,7 +15,6 @@ use ssz_derive::{Decode, Encode}; use std::marker::PhantomData; use superstruct::superstruct; use test_random_derive::TestRandom; -use derivative::Derivative; use tree_hash_derive::TreeHash; #[superstruct( @@ -66,7 +66,10 @@ impl LightClientHeader { block: &SignedBeaconBlock, chain_spec: &ChainSpec, ) -> Result { - let header = match block.fork_name(chain_spec).map_err(|_| Error::InconsistentFork)? { + let header = match block + .fork_name(chain_spec) + .map_err(|_| Error::InconsistentFork)? + { ForkName::Base => return Err(Error::AltairForkNotActive), ForkName::Altair | ForkName::Merge => LightClientHeader::Altair( LightClientHeaderAltair::block_to_light_client_header(block)?, diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index 4de87989a15..a2aaf497fc1 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -3,7 +3,6 @@ use crate::test_utils::TestRandom; use crate::LightClientHeader; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; -use ssz::Decode; use ssz_derive::Encode; use test_random_derive::TestRandom; diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index a7704d1728b..1d8c81cfed4 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -6,7 +6,6 @@ use crate::{ use safe_arith::ArithError; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; -use ssz::Decode; use ssz_derive::Encode; use ssz_types::typenum::{U4, U5, U6}; use std::sync::Arc; diff --git a/testing/ef_tests/tests/tests.rs b/testing/ef_tests/tests/tests.rs index 9168d8178e9..88c1459b2ea 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -277,12 +277,10 @@ mod ssz_static { // Altair and later #[test] fn contribution_and_proof() { - SszStaticHandler::, MinimalEthSpec>::altair_only( - ) - .run(); - SszStaticHandler::, MainnetEthSpec>::altair_only( - ) - .run(); + SszStaticHandler::, MinimalEthSpec>::altair_only() + .run(); + SszStaticHandler::, MainnetEthSpec>::altair_only() + .run(); } // #[test] @@ -305,14 +303,22 @@ mod ssz_static { #[test] fn light_client_header() { - SszStaticHandler::, MinimalEthSpec>::altair_only().run(); - SszStaticHandler::, MainnetEthSpec>::altair_only().run(); + SszStaticHandler::, MinimalEthSpec>::altair_only() + .run(); + SszStaticHandler::, MainnetEthSpec>::altair_only() + .run(); - SszStaticHandler::, MinimalEthSpec>::capella_only().run(); - SszStaticHandler::, MainnetEthSpec>::capella_only().run(); + SszStaticHandler::, MinimalEthSpec>::capella_only( + ) + .run(); + SszStaticHandler::, MainnetEthSpec>::capella_only( + ) + .run(); - SszStaticHandler::, MinimalEthSpec>::deneb_only().run(); - SszStaticHandler::, MainnetEthSpec>::deneb_only().run(); + SszStaticHandler::, MinimalEthSpec>::deneb_only() + .run(); + SszStaticHandler::, MainnetEthSpec>::deneb_only() + .run(); } // #[test] From 57d90fe87c4aad0952fac615bb16aca9e7a73b4c Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Mon, 11 Mar 2024 14:59:58 +0200 Subject: [PATCH 65/72] light client ssz tests --- .../types/src/light_client_finality_update.rs | 11 +- consensus/types/src/light_client_header.rs | 34 +++--- .../src/light_client_optimistic_update.rs | 12 ++- consensus/types/src/light_client_update.rs | 11 +- testing/ef_tests/check_all_files_accessed.py | 4 + testing/ef_tests/src/cases/ssz_static.rs | 49 ++++++++- testing/ef_tests/src/handler.rs | 50 ++++++++- testing/ef_tests/tests/tests.rs | 100 ++++++++++++------ 8 files changed, 216 insertions(+), 55 deletions(-) diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index 10bb0b24946..520ef205f00 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -7,11 +7,20 @@ use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use ssz_derive::Encode; use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; /// A LightClientFinalityUpdate is the update light_client request or received by a gossip that /// signal a new finalized beacon block header for the light client sync protocol. #[derive( - Debug, Clone, PartialEq, Serialize, Deserialize, Encode, arbitrary::Arbitrary, TestRandom, + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + arbitrary::Arbitrary, + TestRandom, + TreeHash, )] #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 70a62369a34..e667b3101d5 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -44,8 +44,7 @@ use tree_hash_derive::TreeHash; #[arbitrary(bound = "E: EthSpec")] pub struct LightClientHeader { pub beacon: BeaconBlockHeader, - #[superstruct(only(Capella, Deneb))] - pub execution_branch: FixedVector, + #[superstruct( only(Capella), partial_getter(rename = "execution_payload_header_capella") @@ -54,6 +53,9 @@ pub struct LightClientHeader { #[superstruct(only(Deneb), partial_getter(rename = "execution_payload_header_deneb"))] pub execution: ExecutionPayloadHeaderDeneb, + #[superstruct(only(Capella, Deneb))] + pub execution_branch: FixedVector, + #[ssz(skip_serializing, skip_deserializing)] #[tree_hash(skip_hashing)] #[serde(skip)] @@ -122,27 +124,29 @@ impl Encode for LightClientHeader { false } - fn ssz_bytes_len(&self) -> usize { - match self { - LightClientHeader::Altair(header) => header.ssz_bytes_len(), - LightClientHeader::Capella(header) => header.ssz_bytes_len(), - LightClientHeader::Deneb(header) => header.ssz_bytes_len(), - } + fn ssz_bytes_len<'a>(&'a self) -> usize { + map_light_client_header_ref!(&'a _, self.to_ref(), |inner, cons| { + cons(inner); + inner.ssz_bytes_len() + }) } fn ssz_fixed_len() -> usize { ssz::BYTES_PER_LENGTH_OFFSET } - fn as_ssz_bytes(&self) -> Vec { - match self { - LightClientHeader::Altair(header) => header.as_ssz_bytes(), - LightClientHeader::Capella(header) => header.as_ssz_bytes(), - LightClientHeader::Deneb(header) => header.as_ssz_bytes(), - } + fn as_ssz_bytes<'a>(&'a self) -> Vec { + map_light_client_header_ref!(&'a _, self.to_ref(), |inner, cons| { + cons(inner); + inner.as_ssz_bytes() + }) } - fn ssz_append(&self, buf: &mut Vec) { + fn ssz_append<'a>(&'a self, buf: &mut Vec) { + // map_light_client_header_ref!(&'a _, self.to_ref(), move |inner, cons| { + // cons(inner); + // inner.ssz_append(buf) + // }); match self { LightClientHeader::Altair(header) => header.ssz_append(buf), LightClientHeader::Capella(header) => header.ssz_append(buf), diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index a2aaf497fc1..c6e47271e60 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -5,11 +5,19 @@ use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use ssz_derive::Encode; use test_random_derive::TestRandom; - +use tree_hash_derive::TreeHash; /// A LightClientOptimisticUpdate is the update we send on each slot, /// it is based off the current unfinalized epoch is verified only against BLS signature. #[derive( - Debug, Clone, PartialEq, Serialize, Deserialize, Encode, arbitrary::Arbitrary, TestRandom, + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + arbitrary::Arbitrary, + TestRandom, + TreeHash, )] #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 1d8c81cfed4..5cc224aa53e 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -11,6 +11,7 @@ use ssz_types::typenum::{U4, U5, U6}; use std::sync::Arc; use test_random_derive::TestRandom; use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; pub const FINALIZED_ROOT_INDEX: usize = 105; pub const CURRENT_SYNC_COMMITTEE_INDEX: usize = 54; @@ -63,7 +64,15 @@ impl From for Error { /// 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]. #[derive( - Debug, Clone, PartialEq, Serialize, Deserialize, Encode, arbitrary::Arbitrary, TestRandom, + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + arbitrary::Arbitrary, + TestRandom, + TreeHash, )] #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] diff --git a/testing/ef_tests/check_all_files_accessed.py b/testing/ef_tests/check_all_files_accessed.py index 68e1fce4141..7629d61827f 100755 --- a/testing/ef_tests/check_all_files_accessed.py +++ b/testing/ef_tests/check_all_files_accessed.py @@ -27,6 +27,10 @@ "tests/.*/.*/ssz_static/PowBlock/", # light_client "tests/.*/.*/light_client", + # LightClientStore + "tests/.*/.*/ssz_static/LightClientStore", + # LightClientSnapshot + "tests/.*/.*/ssz_static/LightClientSnapshot", # One of the EF researchers likes to pack the tarballs on a Mac ".*\.DS_Store.*", # More Mac weirdness. diff --git a/testing/ef_tests/src/cases/ssz_static.rs b/testing/ef_tests/src/cases/ssz_static.rs index 423dc31528f..137410d6986 100644 --- a/testing/ef_tests/src/cases/ssz_static.rs +++ b/testing/ef_tests/src/cases/ssz_static.rs @@ -5,7 +5,10 @@ use crate::decode::{snappy_decode_file, yaml_decode_file}; use serde::Deserialize; use ssz::Decode; use tree_hash::TreeHash; -use types::{BeaconBlock, BeaconState, ForkName, Hash256, SignedBeaconBlock}; +use types::{ + BeaconBlock, BeaconState, ForkName, Hash256, LightClientBootstrap, LightClientFinalityUpdate, + LightClientOptimisticUpdate, LightClientUpdate, SignedBeaconBlock, +}; #[derive(Debug, Clone, Deserialize)] struct SszStaticRoots { @@ -148,3 +151,47 @@ impl Case for SszStaticWithSpec> { Ok(()) } } + +impl Case for SszStaticWithSpec> { + fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> { + check_serialization(&self.value, &self.serialized, |bytes| { + LightClientBootstrap::from_ssz_bytes(bytes, fork_name) + })?; + check_tree_hash(&self.roots.root, self.value.tree_hash_root().as_bytes())?; + Ok(()) + } +} + +impl Case for SszStaticWithSpec> { + fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> { + check_serialization(&self.value, &self.serialized, |bytes| { + LightClientFinalityUpdate::from_ssz_bytes(bytes, fork_name) + })?; + check_tree_hash(&self.roots.root, self.value.tree_hash_root().as_bytes())?; + Ok(()) + } +} + +impl Case for SszStaticWithSpec> { + fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> { + check_serialization(&self.value, &self.serialized, |bytes| { + LightClientOptimisticUpdate::from_ssz_bytes(bytes, fork_name) + })?; + check_tree_hash(&self.roots.root, self.value.tree_hash_root().as_bytes())?; + Ok(()) + } +} + +impl Case for SszStaticWithSpec> { + fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> { + match fork_name { + ForkName::Base | ForkName::Merge => return Ok(()), + _ => {} + }; + check_serialization(&self.value, &self.serialized, |bytes| { + LightClientUpdate::from_ssz_bytes(bytes, fork_name) + })?; + check_tree_hash(&self.roots.root, self.value.tree_hash_root().as_bytes())?; + Ok(()) + } +} diff --git a/testing/ef_tests/src/handler.rs b/testing/ef_tests/src/handler.rs index 0295ff1bd49..d262a116266 100644 --- a/testing/ef_tests/src/handler.rs +++ b/testing/ef_tests/src/handler.rs @@ -243,7 +243,51 @@ pub struct SszStaticTHCHandler(PhantomData<(T, E)>); /// Handler for SSZ types that don't implement `ssz::Decode`. #[derive(Derivative)] #[derivative(Default(bound = ""))] -pub struct SszStaticWithSpecHandler(PhantomData<(T, E)>); +pub struct SszStaticWithSpecHandler { + supported_forks: Vec, + _phantom: PhantomData<(T, E)>, +} + +impl SszStaticWithSpecHandler { + pub fn for_forks(supported_forks: Vec) -> Self { + SszStaticWithSpecHandler { + supported_forks, + _phantom: PhantomData, + } + } + + pub fn base_only() -> Self { + Self::for_forks(vec![ForkName::Base]) + } + + pub fn altair_only() -> Self { + Self::for_forks(vec![ForkName::Altair]) + } + + pub fn merge_only() -> Self { + Self::for_forks(vec![ForkName::Merge]) + } + + pub fn capella_only() -> Self { + Self::for_forks(vec![ForkName::Capella]) + } + + pub fn deneb_only() -> Self { + Self::for_forks(vec![ForkName::Deneb]) + } + + pub fn altair_and_later() -> Self { + Self::for_forks(ForkName::list_all()[1..].to_vec()) + } + + pub fn merge_and_later() -> Self { + Self::for_forks(ForkName::list_all()[2..].to_vec()) + } + + pub fn capella_and_later() -> Self { + Self::for_forks(ForkName::list_all()[3..].to_vec()) + } +} impl Handler for SszStaticHandler where @@ -307,6 +351,10 @@ where fn handler_name(&self) -> String { T::name().into() } + + fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { + self.supported_forks.contains(&fork_name) + } } #[derive(Derivative)] diff --git a/testing/ef_tests/tests/tests.rs b/testing/ef_tests/tests/tests.rs index 88c1459b2ea..2791adb3826 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -248,9 +248,11 @@ mod ssz_static { ssz_static_test!(signed_voluntary_exit, SignedVoluntaryExit); ssz_static_test!(signing_data, SigningData); ssz_static_test!(validator, Validator); - ssz_static_test!(voluntary_exit, VoluntaryExit); + // ssz_static_test!(voluntary_exit, VoluntaryExit); // ssz_static_test!(light_client_bootstrap, SszStaticWithSpecHandler, LightClientBootstrap<_>); - + // ssz_static_test!(light_client_update, SszStaticWithSpecHandler, LightClientUpdate<_>); + // ssz_static_test!(light_client_finality_update, SszStaticWithSpecHandler, LightClientFinalityUpdate<_>); + // ssz_static_test!(light_client_optimistic_update, SszStaticWithSpecHandler, LightClientOptimisticUpdate<_>); // BeaconBlockBody has no internal indicator of which fork it is for, so we test it separately. #[test] fn beacon_block_body() { @@ -283,23 +285,37 @@ mod ssz_static { .run(); } - // #[test] - // fn light_client_bootstrap() { - // SszStaticHandler::, MinimalEthSpec>::altair_and_later() - // .run(); - // SszStaticHandler::, MainnetEthSpec>::altair_and_later() - // .run(); - // } - - // #[test] - // fn light_client_finality_update() { - // SszStaticHandler::, MinimalEthSpec>::altair_and_later( - // ) - // .run(); - // SszStaticHandler::, MainnetEthSpec>::altair_and_later( - // ) - // .run(); - // } + #[test] + fn light_client_bootstrap() { + // SszStaticWithSpecHandler::, MinimalEthSpec>::altair_only() + // .run(); + // SszStaticWithSpecHandler::, MainnetEthSpec>::altair_only() + // .run(); + SszStaticWithSpecHandler::, MinimalEthSpec>::capella_only() + .run(); + SszStaticWithSpecHandler::, MainnetEthSpec>::capella_only() + .run(); + SszStaticWithSpecHandler::, MinimalEthSpec>::deneb_only() + .run(); + SszStaticWithSpecHandler::, MainnetEthSpec>::deneb_only() + .run(); + } + + #[test] + fn light_client_finality_update() { + // SszStaticWithSpecHandler::, MinimalEthSpec>::altair_only() + // .run(); + // SszStaticWithSpecHandler::, MainnetEthSpec>::altair_only() + // .run(); + SszStaticWithSpecHandler::, MinimalEthSpec>::capella_only() + .run(); + SszStaticWithSpecHandler::, MainnetEthSpec>::capella_only() + .run(); + SszStaticWithSpecHandler::, MinimalEthSpec>::deneb_only() + .run(); + SszStaticWithSpecHandler::, MainnetEthSpec>::deneb_only() + .run(); + } #[test] fn light_client_header() { @@ -321,21 +337,37 @@ mod ssz_static { .run(); } - // #[test] - // fn light_client_optimistic_update() { - // SszStaticHandler::, MinimalEthSpec>::altair_and_later( - // ) - // .run(); - // SszStaticHandler::, MainnetEthSpec>::altair_and_later( - // ) - // .run(); - // } - - // #[test] - // fn light_client_update() { - // SszStaticHandler::, MinimalEthSpec>::altair_and_later().run(); - // SszStaticHandler::, MainnetEthSpec>::altair_and_later().run(); - // } + #[test] + fn light_client_optimistic_update() { + // SszStaticWithSpecHandler::, MinimalEthSpec>::altair_only( + // ) + // .run(); + // SszStaticWithSpecHandler::, MainnetEthSpec>::altair_only( + // ) + // .run(); + SszStaticWithSpecHandler::, MinimalEthSpec>::capella_only( + ) + .run(); + SszStaticWithSpecHandler::, MainnetEthSpec>::capella_only( + ) + .run(); + SszStaticWithSpecHandler::, MinimalEthSpec>::deneb_only( + ) + .run(); + SszStaticWithSpecHandler::, MainnetEthSpec>::deneb_only( + ) + .run(); + } + + #[test] + fn light_client_update() { + // SszStaticWithSpecHandler::, MinimalEthSpec>::altair_only().run(); + // SszStaticWithSpecHandler::, MainnetEthSpec>::altair_only().run(); + SszStaticWithSpecHandler::, MinimalEthSpec>::capella_only().run(); + SszStaticWithSpecHandler::, MainnetEthSpec>::capella_only().run(); + SszStaticWithSpecHandler::, MinimalEthSpec>::deneb_only().run(); + SszStaticWithSpecHandler::, MainnetEthSpec>::deneb_only().run(); + } #[test] fn signed_contribution_and_proof() { From 534e80828dc332f9e0799b3443ce1f287c8e50e3 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Mon, 11 Mar 2024 19:26:09 +0200 Subject: [PATCH 66/72] change to superstruct --- ...ght_client_finality_update_verification.rs | 2 +- ...t_client_optimistic_update_verification.rs | 9 +- .../src/light_client_server_cache.rs | 58 ++---- beacon_node/http_api/src/lib.rs | 4 +- .../src/rpc/codec/ssz_snappy.rs | 2 +- .../lighthouse_network/src/rpc/methods.rs | 6 +- consensus/types/src/lib.rs | 20 +- consensus/types/src/light_client_bootstrap.rs | 154 +++++++++------ .../types/src/light_client_finality_update.rs | 178 ++++++++++++----- consensus/types/src/light_client_header.rs | 41 +--- .../src/light_client_optimistic_update.rs | 167 ++++++++++++---- consensus/types/src/light_client_update.rs | 186 ++++++++++++------ testing/ef_tests/check_all_files_accessed.py | 4 - testing/ef_tests/src/handler.rs | 50 +---- testing/ef_tests/src/type_name.rs | 12 ++ testing/ef_tests/tests/tests.rs | 123 ++++++++---- testing/simulator/src/checks.rs | 8 +- 17 files changed, 625 insertions(+), 399 deletions(-) diff --git a/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs b/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs index 35863aa05ff..879fa02f7d9 100644 --- a/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs +++ b/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs @@ -48,7 +48,7 @@ impl VerifiedLightClientFinalityUpdate { // verify that enough time has passed for the block to have been propagated let start_time = chain .slot_clock - .start_of(rcv_finality_update.signature_slot) + .start_of(*rcv_finality_update.signature_slot()) .ok_or(Error::SigSlotStartIsNone)?; let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0); if seen_timestamp + chain.spec.maximum_gossip_clock_disparity() diff --git a/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs b/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs index ad5458945a6..5665adc3ed9 100644 --- a/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs +++ b/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs @@ -52,7 +52,7 @@ impl VerifiedLightClientOptimisticUpdate { // verify that enough time has passed for the block to have been propagated let start_time = chain .slot_clock - .start_of(rcv_optimistic_update.signature_slot) + .start_of(*rcv_optimistic_update.signature_slot()) .ok_or(Error::SigSlotStartIsNone)?; let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0); if seen_timestamp + chain.spec.maximum_gossip_clock_disparity() @@ -65,10 +65,7 @@ impl VerifiedLightClientOptimisticUpdate { let head_block = &head.snapshot.beacon_block; // check if we can process the optimistic update immediately // otherwise queue - let canonical_root = rcv_optimistic_update - .attested_header - .beacon() - .canonical_root(); + let canonical_root = rcv_optimistic_update.get_canonical_root(); if canonical_root != head_block.message().parent_root() { return Err(Error::UnknownBlockParentRoot(canonical_root)); @@ -84,7 +81,7 @@ impl VerifiedLightClientOptimisticUpdate { return Err(Error::InvalidLightClientOptimisticUpdate); } - let parent_root = rcv_optimistic_update.attested_header.beacon().parent_root; + let parent_root = rcv_optimistic_update.get_parent_root(); Ok(Self { light_client_optimistic_update: rcv_optimistic_update, parent_root, diff --git a/beacon_node/beacon_chain/src/light_client_server_cache.rs b/beacon_node/beacon_chain/src/light_client_server_cache.rs index f44b44ca38f..325f06da44c 100644 --- a/beacon_node/beacon_chain/src/light_client_server_cache.rs +++ b/beacon_node/beacon_chain/src/light_client_server_cache.rs @@ -4,15 +4,11 @@ use parking_lot::{Mutex, RwLock}; use slog::{debug, Logger}; use ssz_types::FixedVector; use std::num::NonZeroUsize; -use types::light_client_header::{ - LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, -}; use types::light_client_update::{FinalizedRootProofLen, FINALIZED_ROOT_INDEX}; use types::non_zero_usize::new_non_zero_usize; use types::{ - light_client_update::Error as LightClientError, BeaconBlockRef, BeaconState, ChainSpec, - EthSpec, ForkName, Hash256, LightClientFinalityUpdate, LightClientHeader, - LightClientOptimisticUpdate, SignedBeaconBlock, Slot, SyncAggregate, + BeaconBlockRef, BeaconState, ChainSpec, EthSpec, ForkName, Hash256, LightClientFinalityUpdate, + LightClientOptimisticUpdate, Slot, SyncAggregate, }; /// A prev block cache miss requires to re-generate the state of the post-parent block. Items in the @@ -117,11 +113,12 @@ impl LightClientServerCache { }; if is_latest_optimistic { // can create an optimistic update, that is more recent - *self.latest_optimistic_update.write() = Some(LightClientOptimisticUpdate { - attested_header: block_to_light_client_header(fork_name, &attested_block)?, - sync_aggregate: sync_aggregate.clone(), + *self.latest_optimistic_update.write() = Some(LightClientOptimisticUpdate::new( + &attested_block, + sync_aggregate.clone(), signature_slot, - }); + fork_name, + )?); }; // Spec: Full nodes SHOULD provide the LightClientFinalityUpdate with the highest @@ -137,15 +134,14 @@ impl LightClientServerCache { if let Some(finalized_block) = store.get_full_block(&cached_parts.finalized_block_root)? { - *self.latest_finality_update.write() = Some(LightClientFinalityUpdate { - // TODO: may want to cache this result from latest_optimistic_update if producing a - // light_client header becomes expensive - attested_header: block_to_light_client_header(fork_name, &attested_block)?, - finalized_header: block_to_light_client_header(fork_name, &finalized_block)?, - finality_branch: cached_parts.finality_branch.clone(), - sync_aggregate: sync_aggregate.clone(), + *self.latest_finality_update.write() = Some(LightClientFinalityUpdate::new( + &attested_block, + &finalized_block, + cached_parts.finality_branch.clone(), + sync_aggregate.clone(), signature_slot, - }); + fork_name, + )?); } else { debug!( log, @@ -231,10 +227,11 @@ fn is_latest_finality_update( attested_slot: Slot, signature_slot: Slot, ) -> bool { - if attested_slot > prev.attested_header.beacon().slot { + let prev_slot = prev.get_slot(); + if attested_slot > prev_slot { true } else { - attested_slot == prev.attested_header.beacon().slot && signature_slot > prev.signature_slot + attested_slot == prev_slot && signature_slot > *prev.signature_slot() } } @@ -247,25 +244,10 @@ fn is_latest_optimistic_update( attested_slot: Slot, signature_slot: Slot, ) -> bool { - if attested_slot > prev.attested_header.beacon().slot { + let prev_slot = prev.get_slot(); + if attested_slot > prev_slot { true } else { - attested_slot == prev.attested_header.beacon().slot && signature_slot > prev.signature_slot + attested_slot == prev_slot && signature_slot > *prev.signature_slot() } } - -fn block_to_light_client_header( - fork_name: ForkName, - block: &SignedBeaconBlock, -) -> Result, BeaconChainError> { - let light_client_header = match fork_name { - ForkName::Base => return Err(LightClientError::AltairForkNotActive.into()), - ForkName::Merge | ForkName::Altair => { - LightClientHeaderAltair::block_to_light_client_header(block)?.into() - } - ForkName::Capella => LightClientHeaderCapella::block_to_light_client_header(block)?.into(), - ForkName::Deneb => LightClientHeaderDeneb::block_to_light_client_header(block)?.into(), - }; - - Ok(light_client_header) -} diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index b39450d7354..dbd720e425d 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -2337,7 +2337,7 @@ pub fn serve( let fork_name = chain .spec - .fork_name_at_slot::(update.signature_slot); + .fork_name_at_slot::(*update.signature_slot()); match accept_header { Some(api_types::Accept::Ssz) => Response::builder() .status(200) @@ -2384,7 +2384,7 @@ pub fn serve( let fork_name = chain .spec - .fork_name_at_slot::(update.signature_slot); + .fork_name_at_slot::(*update.signature_slot()); match accept_header { Some(api_types::Accept::Ssz) => Response::builder() .status(200) diff --git a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs index c7752098604..bdd566cce6b 100644 --- a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs +++ b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs @@ -592,7 +592,7 @@ fn handle_rpc_response( )))), SupportedProtocol::LightClientBootstrapV1 => match fork_name { Some(fork_name) => Ok(Some(RPCResponse::LightClientBootstrap(Arc::new( - LightClientBootstrap::from_ssz_bytes(decoded_buffer, fork_name)?, + LightClientBootstrap::from_ssz_bytes_for_fork(decoded_buffer, fork_name)?, )))), None => Err(RPCError::ErrorResponse( RPCResponseErrorCode::InvalidRequest, diff --git a/beacon_node/lighthouse_network/src/rpc/methods.rs b/beacon_node/lighthouse_network/src/rpc/methods.rs index 415a3ce9c92..9afa1d0f7ef 100644 --- a/beacon_node/lighthouse_network/src/rpc/methods.rs +++ b/beacon_node/lighthouse_network/src/rpc/methods.rs @@ -566,11 +566,7 @@ impl std::fmt::Display for RPCResponse { RPCResponse::Pong(ping) => write!(f, "Pong: {}", ping.data), RPCResponse::MetaData(metadata) => write!(f, "Metadata: {}", metadata.seq_number()), RPCResponse::LightClientBootstrap(bootstrap) => { - write!( - f, - "LightClientBootstrap Slot: {}", - bootstrap.header.beacon().slot - ) + write!(f, "LightClientBootstrap Slot: {}", bootstrap.get_slot()) } } } diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index dad7ab9f999..e216a5da66b 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -152,13 +152,25 @@ pub use crate::fork_versioned_response::{ForkVersionDeserialize, ForkVersionedRe pub use crate::graffiti::{Graffiti, GRAFFITI_BYTES_LEN}; pub use crate::historical_batch::HistoricalBatch; pub use crate::indexed_attestation::IndexedAttestation; -pub use crate::light_client_bootstrap::LightClientBootstrap; -pub use crate::light_client_finality_update::LightClientFinalityUpdate; +pub use crate::light_client_bootstrap::{ + LightClientBootstrap, LightClientBootstrapAltair, LightClientBootstrapCapella, + LightClientBootstrapDeneb, +}; +pub use crate::light_client_finality_update::{ + LightClientFinalityUpdate, LightClientFinalityUpdateAltair, LightClientFinalityUpdateCapella, + LightClientFinalityUpdateDeneb, +}; pub use crate::light_client_header::{ LightClientHeader, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, }; -pub use crate::light_client_optimistic_update::LightClientOptimisticUpdate; -pub use crate::light_client_update::{Error as LightClientError, LightClientUpdate}; +pub use crate::light_client_optimistic_update::{ + LightClientOptimisticUpdate, LightClientOptimisticUpdateAltair, + LightClientOptimisticUpdateCapella, LightClientOptimisticUpdateDeneb, +}; +pub use crate::light_client_update::{ + Error as LightClientError, LightClientUpdate, LightClientUpdateAltair, + LightClientUpdateCapella, LightClientUpdateDeneb, +}; pub use crate::participation_flags::ParticipationFlags; pub use crate::participation_list::ParticipationList; pub use crate::payload::{ diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 7f3340cb992..1a3e2049c32 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -1,85 +1,137 @@ use super::{BeaconState, EthSpec, FixedVector, Hash256, SyncCommittee}; use crate::{ light_client_update::*, test_utils::TestRandom, ChainSpec, ForkName, ForkVersionDeserialize, - LightClientHeader, SignedBeaconBlock, + LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, SignedBeaconBlock, + Slot, }; use derivative::Derivative; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; -use ssz_derive::Encode; +use ssz::Decode; +use ssz_derive::{Decode, Encode}; use std::sync::Arc; +use superstruct::superstruct; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; /// A LightClientBootstrap is the initializer we send over to light_client nodes /// that are trying to generate their basic storage when booting up. +#[superstruct( + variants(Altair, Capella, Deneb), + variant_attributes( + derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Derivative, + Decode, + Encode, + TestRandom, + arbitrary::Arbitrary, + TreeHash, + ), + serde(bound = "E: EthSpec", deny_unknown_fields), + arbitrary(bound = "E: EthSpec"), + ) +)] #[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - Derivative, - TreeHash, - arbitrary::Arbitrary, - TestRandom, + Debug, Clone, Serialize, TreeHash, Encode, Deserialize, arbitrary::Arbitrary, PartialEq, )] -#[serde(bound = "T: EthSpec")] -#[arbitrary(bound = "T: EthSpec")] -pub struct LightClientBootstrap { +#[serde(untagged)] +#[tree_hash(enum_behaviour = "transparent")] +#[ssz(enum_behaviour = "transparent")] +#[serde(bound = "E: EthSpec", deny_unknown_fields)] +#[arbitrary(bound = "E: EthSpec")] +pub struct LightClientBootstrap { /// The requested beacon block header. - pub header: LightClientHeader, + #[superstruct(only(Altair), partial_getter(rename = "header_altair"))] + pub header: LightClientHeaderAltair, + #[superstruct(only(Capella), partial_getter(rename = "header_capella"))] + pub header: LightClientHeaderCapella, + #[superstruct(only(Deneb), partial_getter(rename = "header_deneb"))] + pub header: LightClientHeaderDeneb, /// The `SyncCommittee` used in the requested period. - pub current_sync_committee: Arc>, + pub current_sync_committee: Arc>, /// Merkle proof for sync committee pub current_sync_committee_branch: FixedVector, } impl LightClientBootstrap { - pub fn from_beacon_state( - beacon_state: &mut BeaconState, - block: &SignedBeaconBlock, - chain_spec: &ChainSpec, - ) -> Result { - let mut header = beacon_state.latest_block_header().clone(); - header.state_root = beacon_state.update_tree_hash_cache()?; - let current_sync_committee_branch = - beacon_state.compute_merkle_proof(CURRENT_SYNC_COMMITTEE_INDEX)?; - - let header = LightClientHeader::::block_to_light_client_header(block, chain_spec)?; - - Ok(LightClientBootstrap { - header, - current_sync_committee: beacon_state.current_sync_committee()?.clone(), - current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, + pub fn get_slot<'a>(&'a self) -> Slot { + map_light_client_bootstrap_ref!(&'a _, self.to_ref(), |inner, cons| { + cons(inner); + inner.header.beacon.slot }) } pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result { - let mut builder = ssz::SszDecoderBuilder::new(bytes); - builder.register_anonymous_variable_length_item()?; - builder.register_type::>()?; - builder.register_type::>()?; - let mut decoder = builder.build()?; - let header = decoder - .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; - let current_sync_committee = decoder.decode_next()?; - let current_sync_committee_branch = decoder.decode_next()?; + let bootstrap = match fork_name { + ForkName::Altair | ForkName::Merge => { + let header = LightClientBootstrapAltair::from_ssz_bytes(bytes)?; + Self::Altair(header) + } + ForkName::Capella => { + let header = LightClientBootstrapCapella::from_ssz_bytes(bytes)?; + Self::Capella(header) + } + ForkName::Deneb => { + let header = LightClientBootstrapDeneb::from_ssz_bytes(bytes)?; + Self::Deneb(header) + } + ForkName::Base => { + return Err(ssz::DecodeError::BytesInvalid(format!( + "LightClientBootstrap decoding for {fork_name} not implemented" + ))) + } + }; - Ok(Self { - header, - current_sync_committee: Arc::new(current_sync_committee), - current_sync_committee_branch, - }) + Ok(bootstrap) } + /// Custom SSZ decoder that takes a `ForkName` as context. pub fn from_ssz_bytes_for_fork( bytes: &[u8], fork_name: ForkName, ) -> Result { Self::from_ssz_bytes(bytes, fork_name) } + + pub fn from_beacon_state( + beacon_state: &mut BeaconState, + block: &SignedBeaconBlock, + chain_spec: &ChainSpec, + ) -> Result { + let mut header = beacon_state.latest_block_header().clone(); + header.state_root = beacon_state.update_tree_hash_cache()?; + let current_sync_committee_branch = + beacon_state.compute_merkle_proof(CURRENT_SYNC_COMMITTEE_INDEX)?; + + let light_client_bootstrap = match block + .fork_name(chain_spec) + .map_err(|_| Error::InconsistentFork)? + { + ForkName::Base => return Err(Error::AltairForkNotActive), + ForkName::Altair | ForkName::Merge => Self::Altair(LightClientBootstrapAltair { + header: LightClientHeaderAltair::block_to_light_client_header(block)?, + current_sync_committee: beacon_state.current_sync_committee()?.clone(), + current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, + }), + ForkName::Capella => Self::Capella(LightClientBootstrapCapella { + header: LightClientHeaderCapella::block_to_light_client_header(block)?, + current_sync_committee: beacon_state.current_sync_committee()?.clone(), + current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, + }), + ForkName::Deneb => Self::Deneb(LightClientBootstrapDeneb { + header: LightClientHeaderDeneb::block_to_light_client_header(block)?, + current_sync_committee: beacon_state.current_sync_committee()?.clone(), + current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, + }), + }; + + Ok(light_client_bootstrap) + } } impl ForkVersionDeserialize for LightClientBootstrap { @@ -99,11 +151,3 @@ impl ForkVersionDeserialize for LightClientBootstrap { } } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::MainnetEthSpec; - - ssz_tests_by_fork!(LightClientBootstrap, ForkName::Deneb); -} diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index 520ef205f00..bdd23c453b4 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -1,69 +1,159 @@ use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate}; use crate::{ light_client_update::*, test_utils::TestRandom, ForkName, ForkVersionDeserialize, - LightClientHeader, + LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, SignedBeaconBlock, }; +use derivative::Derivative; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; +use ssz::Decode; +use ssz_derive::Decode; use ssz_derive::Encode; +use superstruct::superstruct; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; -/// A LightClientFinalityUpdate is the update light_client request or received by a gossip that -/// signal a new finalized beacon block header for the light client sync protocol. +#[superstruct( + variants(Altair, Capella, Deneb), + variant_attributes( + derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Derivative, + Decode, + Encode, + TestRandom, + arbitrary::Arbitrary, + TreeHash, + ), + serde(bound = "E: EthSpec", deny_unknown_fields), + arbitrary(bound = "E: EthSpec"), + ) +)] #[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - arbitrary::Arbitrary, - TestRandom, - TreeHash, + Debug, Clone, Serialize, Encode, TreeHash, Deserialize, arbitrary::Arbitrary, PartialEq, )] -#[serde(bound = "T: EthSpec")] -#[arbitrary(bound = "T: EthSpec")] -pub struct LightClientFinalityUpdate { +#[serde(untagged)] +#[tree_hash(enum_behaviour = "transparent")] +#[ssz(enum_behaviour = "transparent")] +#[serde(bound = "E: EthSpec", deny_unknown_fields)] +#[arbitrary(bound = "E: EthSpec")] +pub struct LightClientFinalityUpdate { /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: LightClientHeader, + #[superstruct(only(Altair), partial_getter(rename = "attested_header_altair"))] + pub attested_header: LightClientHeaderAltair, + #[superstruct(only(Capella), partial_getter(rename = "attested_header_capella"))] + pub attested_header: LightClientHeaderCapella, + #[superstruct(only(Deneb), partial_getter(rename = "attested_header_deneb"))] + pub attested_header: LightClientHeaderDeneb, /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). - pub finalized_header: LightClientHeader, + #[superstruct(only(Altair), partial_getter(rename = "finalized_header_altair"))] + pub finalized_header: LightClientHeaderAltair, + #[superstruct(only(Capella), partial_getter(rename = "finalized_header_capella"))] + pub finalized_header: LightClientHeaderCapella, + #[superstruct(only(Deneb), partial_getter(rename = "finalized_header_deneb"))] + pub finalized_header: LightClientHeaderDeneb, /// Merkle proof attesting finalized header. pub finality_branch: FixedVector, /// current sync aggreggate - pub sync_aggregate: SyncAggregate, + pub sync_aggregate: SyncAggregate, /// Slot of the sync aggregated singature pub signature_slot: Slot, } impl LightClientFinalityUpdate { - pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result { - let mut builder = ssz::SszDecoderBuilder::new(bytes); - builder.register_anonymous_variable_length_item()?; - builder.register_anonymous_variable_length_item()?; - builder.register_type::>()?; - builder.register_type::>()?; - builder.register_type::()?; - let mut decoder = builder.build()?; + pub fn new( + attested_block: &SignedBeaconBlock, + finalized_block: &SignedBeaconBlock, + finality_branch: FixedVector, + sync_aggregate: SyncAggregate, + signature_slot: Slot, + fork_name: ForkName, + ) -> Result { + let finality_update = match fork_name { + ForkName::Altair | ForkName::Merge => { + let finality_update = LightClientFinalityUpdateAltair { + attested_header: LightClientHeaderAltair::block_to_light_client_header( + attested_block, + )?, + finalized_header: LightClientHeaderAltair::block_to_light_client_header( + finalized_block, + )?, + finality_branch, + sync_aggregate, + 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 => { + 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::Base => return Err(Error::AltairForkNotActive), + }; - let attested_header = decoder - .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; - let finalized_header = decoder - .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; - let finality_branch = decoder.decode_next()?; - let sync_aggregate = decoder.decode_next()?; - let signature_slot = decoder.decode_next()?; + Ok(finality_update) + } - Ok(Self { - attested_header, - finalized_header, - finality_branch, - sync_aggregate, - signature_slot, + pub fn get_slot<'a>(&'a self) -> Slot { + map_light_client_finality_update_ref!(&'a _, self.to_ref(), |inner, cons| { + cons(inner); + inner.attested_header.beacon.slot }) } + pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result { + let finality_update = match fork_name { + ForkName::Altair | ForkName::Merge => { + let finality_update = LightClientFinalityUpdateAltair::from_ssz_bytes(bytes)?; + Self::Altair(finality_update) + } + ForkName::Capella => { + let finality_update = LightClientFinalityUpdateCapella::from_ssz_bytes(bytes)?; + Self::Capella(finality_update) + } + ForkName::Deneb => { + let finality_update = LightClientFinalityUpdateDeneb::from_ssz_bytes(bytes)?; + Self::Deneb(finality_update) + } + ForkName::Base => { + return Err(ssz::DecodeError::BytesInvalid(format!( + "LightClientFinalityUpdate decoding for {fork_name} not implemented" + ))) + } + }; + + Ok(finality_update) + } + + /// Custom SSZ decoder that takes a `ForkName` as context. pub fn from_ssz_bytes_for_fork( bytes: &[u8], fork_name: ForkName, @@ -88,12 +178,4 @@ impl ForkVersionDeserialize for LightClientFinalityUpdate { ))), } } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::MainnetEthSpec; - - ssz_tests_by_fork!(LightClientFinalityUpdate, ForkName::Deneb); -} +} \ No newline at end of file diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index e667b3101d5..6f49fed5cbb 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -10,7 +10,7 @@ use crate::{ use derivative::Derivative; use serde::{Deserialize, Serialize}; use serde_json; -use ssz::{Decode, Encode}; +use ssz::Decode; use ssz_derive::{Decode, Encode}; use std::marker::PhantomData; use superstruct::superstruct; @@ -37,9 +37,10 @@ use tree_hash_derive::TreeHash; arbitrary(bound = "E: EthSpec"), ) )] -#[derive(Debug, Clone, Serialize, TreeHash, Deserialize, arbitrary::Arbitrary, PartialEq)] +#[derive(Debug, Clone, Serialize, TreeHash, Encode, Deserialize, arbitrary::Arbitrary, PartialEq)] #[serde(untagged)] #[tree_hash(enum_behaviour = "transparent")] +#[ssz(enum_behaviour = "transparent")] #[serde(bound = "E: EthSpec", deny_unknown_fields)] #[arbitrary(bound = "E: EthSpec")] pub struct LightClientHeader { @@ -119,42 +120,6 @@ impl LightClientHeader { } } -impl Encode for LightClientHeader { - fn is_ssz_fixed_len() -> bool { - false - } - - fn ssz_bytes_len<'a>(&'a self) -> usize { - map_light_client_header_ref!(&'a _, self.to_ref(), |inner, cons| { - cons(inner); - inner.ssz_bytes_len() - }) - } - - fn ssz_fixed_len() -> usize { - ssz::BYTES_PER_LENGTH_OFFSET - } - - fn as_ssz_bytes<'a>(&'a self) -> Vec { - map_light_client_header_ref!(&'a _, self.to_ref(), |inner, cons| { - cons(inner); - inner.as_ssz_bytes() - }) - } - - fn ssz_append<'a>(&'a self, buf: &mut Vec) { - // map_light_client_header_ref!(&'a _, self.to_ref(), move |inner, cons| { - // cons(inner); - // inner.ssz_append(buf) - // }); - match self { - LightClientHeader::Altair(header) => header.ssz_append(buf), - LightClientHeader::Capella(header) => header.ssz_append(buf), - LightClientHeader::Deneb(header) => header.ssz_append(buf), - } - } -} - impl LightClientHeaderAltair { pub fn block_to_light_client_header(block: &SignedBeaconBlock) -> Result { Ok(LightClientHeaderAltair { diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index c6e47271e60..9206fedc193 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -1,55 +1,154 @@ use super::{EthSpec, ForkName, ForkVersionDeserialize, Slot, SyncAggregate}; use crate::test_utils::TestRandom; -use crate::LightClientHeader; +use crate::{ + light_client_update::*, LightClientHeaderAltair, LightClientHeaderCapella, + LightClientHeaderDeneb, SignedBeaconBlock, +}; +use derivative::Derivative; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; +use ssz::Decode; +use ssz_derive::Decode; use ssz_derive::Encode; +use superstruct::superstruct; use test_random_derive::TestRandom; +use tree_hash::Hash256; use tree_hash_derive::TreeHash; + /// A LightClientOptimisticUpdate is the update we send on each slot, /// it is based off the current unfinalized epoch is verified only against BLS signature. +#[superstruct( + variants(Altair, Capella, Deneb), + variant_attributes( + derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Derivative, + Decode, + Encode, + TestRandom, + arbitrary::Arbitrary, + TreeHash, + ), + serde(bound = "E: EthSpec", deny_unknown_fields), + arbitrary(bound = "E: EthSpec"), + ) +)] #[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - arbitrary::Arbitrary, - TestRandom, - TreeHash, + Debug, Clone, Serialize, Encode, TreeHash, Deserialize, arbitrary::Arbitrary, PartialEq, )] -#[serde(bound = "T: EthSpec")] -#[arbitrary(bound = "T: EthSpec")] -pub struct LightClientOptimisticUpdate { +#[serde(untagged)] +#[tree_hash(enum_behaviour = "transparent")] +#[ssz(enum_behaviour = "transparent")] +#[serde(bound = "E: EthSpec", deny_unknown_fields)] +#[arbitrary(bound = "E: EthSpec")] +pub struct LightClientOptimisticUpdate { /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: LightClientHeader, + #[superstruct(only(Altair), partial_getter(rename = "attested_header_altair"))] + pub attested_header: LightClientHeaderAltair, + #[superstruct(only(Capella), partial_getter(rename = "attested_header_capella"))] + pub attested_header: LightClientHeaderCapella, + #[superstruct(only(Deneb), partial_getter(rename = "attested_header_deneb"))] + pub attested_header: LightClientHeaderDeneb, /// current sync aggreggate - pub sync_aggregate: SyncAggregate, + pub sync_aggregate: SyncAggregate, /// Slot of the sync aggregated singature pub signature_slot: Slot, } impl LightClientOptimisticUpdate { - pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result { - let mut builder = ssz::SszDecoderBuilder::new(bytes); - builder.register_anonymous_variable_length_item()?; - builder.register_type::>()?; - builder.register_type::()?; - let mut decoder = builder.build()?; + pub fn new( + attested_block: &SignedBeaconBlock, + sync_aggregate: SyncAggregate, + signature_slot: Slot, + fork_name: ForkName, + ) -> Result { + let optimistic_update = match fork_name { + ForkName::Altair | ForkName::Merge => { + let optimistic_update = LightClientOptimisticUpdateAltair { + attested_header: LightClientHeaderAltair::block_to_light_client_header( + attested_block, + )?, + sync_aggregate, + signature_slot, + }; + Self::Altair(optimistic_update) + } + ForkName::Capella => { + let optimistic_update = LightClientOptimisticUpdateCapella { + attested_header: LightClientHeaderCapella::block_to_light_client_header( + attested_block, + )?, + sync_aggregate, + signature_slot, + }; + Self::Capella(optimistic_update) + } + ForkName::Deneb => { + let optimistic_update = LightClientOptimisticUpdateDeneb { + attested_header: LightClientHeaderDeneb::block_to_light_client_header( + attested_block, + )?, + sync_aggregate, + signature_slot, + }; + Self::Deneb(optimistic_update) + } + ForkName::Base => return Err(Error::AltairForkNotActive), + }; + + Ok(optimistic_update) + } - let attested_header = decoder - .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; - let sync_aggregate = decoder.decode_next()?; - let signature_slot = decoder.decode_next()?; + pub fn get_slot<'a>(&'a self) -> Slot { + map_light_client_optimistic_update_ref!(&'a _, self.to_ref(), |inner, cons| { + cons(inner); + inner.attested_header.beacon.slot + }) + } - Ok(Self { - attested_header, - sync_aggregate, - signature_slot, + pub fn get_canonical_root<'a>(&'a self) -> Hash256 { + map_light_client_optimistic_update_ref!(&'a _, self.to_ref(), |inner, cons| { + cons(inner); + inner.attested_header.beacon.canonical_root() }) } + pub fn get_parent_root<'a>(&'a self) -> Hash256 { + map_light_client_optimistic_update_ref!(&'a _, self.to_ref(), |inner, cons| { + cons(inner); + inner.attested_header.beacon.parent_root + }) + } + + pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result { + let optimistic_update = match fork_name { + ForkName::Altair | ForkName::Merge => { + let optimistic_update = LightClientOptimisticUpdateAltair::from_ssz_bytes(bytes)?; + Self::Altair(optimistic_update) + } + ForkName::Capella => { + let optimistic_update = LightClientOptimisticUpdateCapella::from_ssz_bytes(bytes)?; + Self::Capella(optimistic_update) + } + ForkName::Deneb => { + let optimistic_update = LightClientOptimisticUpdateDeneb::from_ssz_bytes(bytes)?; + Self::Deneb(optimistic_update) + } + ForkName::Base => { + return Err(ssz::DecodeError::BytesInvalid(format!( + "LightClientOptimisticUpdate decoding for {fork_name} not implemented" + ))) + } + }; + + Ok(optimistic_update) + } + + /// Custom SSZ decoder that takes a `ForkName` as context. pub fn from_ssz_bytes_for_fork( bytes: &[u8], fork_name: ForkName, @@ -74,12 +173,4 @@ impl ForkVersionDeserialize for LightClientOptimisticUpdate { ))), } } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::MainnetEthSpec; - - ssz_tests_by_fork!(LightClientOptimisticUpdate, ForkName::Deneb); -} +} \ No newline at end of file diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 5cc224aa53e..32b1fe76ecf 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -1,14 +1,19 @@ use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee}; use crate::{ beacon_state, test_utils::TestRandom, BeaconBlock, BeaconBlockHeader, BeaconState, ChainSpec, - ForkName, ForkVersionDeserialize, LightClientHeader, SignedBeaconBlock, + ForkName, ForkVersionDeserialize, LightClientHeaderAltair, LightClientHeaderCapella, + LightClientHeaderDeneb, SignedBeaconBlock, }; +use derivative::Derivative; use safe_arith::ArithError; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; +use ssz::Decode; +use ssz_derive::Decode; use ssz_derive::Encode; use ssz_types::typenum::{U4, U5, U6}; use std::sync::Arc; +use superstruct::superstruct; use test_random_derive::TestRandom; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; @@ -63,33 +68,58 @@ impl From for Error { /// A LightClientUpdate is the update we request solely to either complete the bootstrapping process, /// 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]. +#[superstruct( + variants(Altair, Capella, Deneb), + variant_attributes( + derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Derivative, + Decode, + Encode, + TestRandom, + arbitrary::Arbitrary, + TreeHash, + ), + serde(bound = "E: EthSpec", deny_unknown_fields), + arbitrary(bound = "E: EthSpec"), + ) +)] #[derive( - Debug, - Clone, - PartialEq, - Serialize, - Deserialize, - Encode, - arbitrary::Arbitrary, - TestRandom, - TreeHash, + Debug, Clone, Serialize, Encode, TreeHash, Deserialize, arbitrary::Arbitrary, PartialEq, )] -#[serde(bound = "T: EthSpec")] -#[arbitrary(bound = "T: EthSpec")] -pub struct LightClientUpdate { +#[serde(untagged)] +#[tree_hash(enum_behaviour = "transparent")] +#[ssz(enum_behaviour = "transparent")] +#[serde(bound = "E: EthSpec", deny_unknown_fields)] +#[arbitrary(bound = "E: EthSpec")] +pub struct LightClientUpdate { /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: LightClientHeader, + #[superstruct(only(Altair), partial_getter(rename = "attested_header_altair"))] + pub attested_header: LightClientHeaderAltair, + #[superstruct(only(Capella), partial_getter(rename = "attested_header_capella"))] + pub attested_header: LightClientHeaderCapella, + #[superstruct(only(Deneb), partial_getter(rename = "attested_header_deneb"))] + pub attested_header: LightClientHeaderDeneb, /// The `SyncCommittee` used in the next period. - pub next_sync_committee: Arc>, + pub next_sync_committee: Arc>, /// Merkle proof for next sync committee pub next_sync_committee_branch: FixedVector, /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). - pub finalized_header: LightClientHeader, + #[superstruct(only(Altair), partial_getter(rename = "finalized_header_altair"))] + pub finalized_header: LightClientHeaderAltair, + #[superstruct(only(Capella), partial_getter(rename = "finalized_header_capella"))] + pub finalized_header: LightClientHeaderCapella, + #[superstruct(only(Deneb), partial_getter(rename = "finalized_header_deneb"))] + pub finalized_header: LightClientHeaderDeneb, /// Merkle proof attesting finalized header. pub finality_branch: FixedVector, /// current sync aggreggate - pub sync_aggregate: SyncAggregate, - /// Slot of the sync aggregated singature + pub sync_aggregate: SyncAggregate, + /// Slot of the sync aggregated signature pub signature_slot: Slot, } @@ -151,52 +181,83 @@ impl LightClientUpdate { attested_state.compute_merkle_proof(NEXT_SYNC_COMMITTEE_INDEX)?; let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; - let attested_header = - LightClientHeader::block_to_light_client_header(attested_block, chain_spec)?; - let finalized_header = - LightClientHeader::block_to_light_client_header(finalized_block, chain_spec)?; - - Ok(Self { - 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(), - }) + let light_client_update = match attested_block + .fork_name(chain_spec) + .map_err(|_| Error::InconsistentFork)? + { + ForkName::Base => return Err(Error::AltairForkNotActive), + ForkName::Altair | ForkName::Merge => { + let attested_header = + LightClientHeaderAltair::block_to_light_client_header(attested_block)?; + let finalized_header = + LightClientHeaderAltair::block_to_light_client_header(finalized_block)?; + Self::Altair(LightClientUpdateAltair { + 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(), + }) + } + ForkName::Capella => { + let attested_header = + LightClientHeaderCapella::block_to_light_client_header(attested_block)?; + let finalized_header = + LightClientHeaderCapella::block_to_light_client_header(finalized_block)?; + Self::Capella(LightClientUpdateCapella { + 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(), + }) + } + ForkName::Deneb => { + let attested_header = + LightClientHeaderDeneb::block_to_light_client_header(attested_block)?; + let finalized_header = + LightClientHeaderDeneb::block_to_light_client_header(finalized_block)?; + Self::Deneb(LightClientUpdateDeneb { + 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(), + }) + } + }; + + Ok(light_client_update) } pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result { - let mut builder = ssz::SszDecoderBuilder::new(bytes); - builder.register_anonymous_variable_length_item()?; - builder.register_type::>()?; - builder.register_type::>()?; - builder.register_anonymous_variable_length_item()?; - builder.register_type::>()?; - builder.register_type::>()?; - builder.register_type::()?; - let mut decoder = builder.build()?; - - let attested_header = decoder - .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; - let next_sync_committee = decoder.decode_next()?; - let next_sync_committee_branch = decoder.decode_next()?; - let finalized_header = decoder - .decode_next_with(|bytes| LightClientHeader::from_ssz_bytes(bytes, fork_name))?; - let finality_branch = decoder.decode_next()?; - let sync_aggregate = decoder.decode_next()?; - let signature_slot = decoder.decode_next()?; - - Ok(Self { - attested_header, - next_sync_committee: Arc::new(next_sync_committee), - next_sync_committee_branch, - finalized_header, - finality_branch, - sync_aggregate, - signature_slot, - }) + let update = match fork_name { + ForkName::Altair | ForkName::Merge => { + let update = LightClientUpdateAltair::from_ssz_bytes(bytes)?; + Self::Altair(update) + } + ForkName::Capella => { + let update = LightClientUpdateCapella::from_ssz_bytes(bytes)?; + Self::Capella(update) + } + ForkName::Deneb => { + let update = LightClientUpdateDeneb::from_ssz_bytes(bytes)?; + Self::Deneb(update) + } + ForkName::Base => { + return Err(ssz::DecodeError::BytesInvalid(format!( + "LightClientUpdate decoding for {fork_name} not implemented" + ))) + } + }; + + Ok(update) } pub fn from_ssz_bytes_for_fork( @@ -210,11 +271,8 @@ impl LightClientUpdate { #[cfg(test)] mod tests { use super::*; - use crate::MainnetEthSpec; use ssz_types::typenum::Unsigned; - ssz_tests_by_fork!(LightClientUpdate, ForkName::Deneb); - #[test] fn finalized_root_params() { assert!(2usize.pow(FINALIZED_ROOT_PROOF_LEN as u32) <= FINALIZED_ROOT_INDEX); diff --git a/testing/ef_tests/check_all_files_accessed.py b/testing/ef_tests/check_all_files_accessed.py index 7629d61827f..68e1fce4141 100755 --- a/testing/ef_tests/check_all_files_accessed.py +++ b/testing/ef_tests/check_all_files_accessed.py @@ -27,10 +27,6 @@ "tests/.*/.*/ssz_static/PowBlock/", # light_client "tests/.*/.*/light_client", - # LightClientStore - "tests/.*/.*/ssz_static/LightClientStore", - # LightClientSnapshot - "tests/.*/.*/ssz_static/LightClientSnapshot", # One of the EF researchers likes to pack the tarballs on a Mac ".*\.DS_Store.*", # More Mac weirdness. diff --git a/testing/ef_tests/src/handler.rs b/testing/ef_tests/src/handler.rs index d262a116266..0295ff1bd49 100644 --- a/testing/ef_tests/src/handler.rs +++ b/testing/ef_tests/src/handler.rs @@ -243,51 +243,7 @@ pub struct SszStaticTHCHandler(PhantomData<(T, E)>); /// Handler for SSZ types that don't implement `ssz::Decode`. #[derive(Derivative)] #[derivative(Default(bound = ""))] -pub struct SszStaticWithSpecHandler { - supported_forks: Vec, - _phantom: PhantomData<(T, E)>, -} - -impl SszStaticWithSpecHandler { - pub fn for_forks(supported_forks: Vec) -> Self { - SszStaticWithSpecHandler { - supported_forks, - _phantom: PhantomData, - } - } - - pub fn base_only() -> Self { - Self::for_forks(vec![ForkName::Base]) - } - - pub fn altair_only() -> Self { - Self::for_forks(vec![ForkName::Altair]) - } - - pub fn merge_only() -> Self { - Self::for_forks(vec![ForkName::Merge]) - } - - pub fn capella_only() -> Self { - Self::for_forks(vec![ForkName::Capella]) - } - - pub fn deneb_only() -> Self { - Self::for_forks(vec![ForkName::Deneb]) - } - - pub fn altair_and_later() -> Self { - Self::for_forks(ForkName::list_all()[1..].to_vec()) - } - - pub fn merge_and_later() -> Self { - Self::for_forks(ForkName::list_all()[2..].to_vec()) - } - - pub fn capella_and_later() -> Self { - Self::for_forks(ForkName::list_all()[3..].to_vec()) - } -} +pub struct SszStaticWithSpecHandler(PhantomData<(T, E)>); impl Handler for SszStaticHandler where @@ -351,10 +307,6 @@ where fn handler_name(&self) -> String { T::name().into() } - - fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { - self.supported_forks.contains(&fork_name) - } } #[derive(Derivative)] diff --git a/testing/ef_tests/src/type_name.rs b/testing/ef_tests/src/type_name.rs index 62bba571d76..947f88ef964 100644 --- a/testing/ef_tests/src/type_name.rs +++ b/testing/ef_tests/src/type_name.rs @@ -74,13 +74,25 @@ type_name!(ForkData); type_name_generic!(HistoricalBatch); type_name_generic!(IndexedAttestation); type_name_generic!(LightClientBootstrap); +type_name_generic!(LightClientBootstrapAltair, "LightClientBootstrap"); +type_name_generic!(LightClientBootstrapCapella, "LightClientBootstrap"); +type_name_generic!(LightClientBootstrapDeneb, "LightClientBootstrap"); type_name_generic!(LightClientFinalityUpdate); +type_name_generic!(LightClientFinalityUpdateAltair, "LightClientFinalityUpdate"); +type_name_generic!(LightClientFinalityUpdateCapella, "LightClientFinalityUpdate"); +type_name_generic!(LightClientFinalityUpdateDeneb, "LightClientFinalityUpdate"); type_name_generic!(LightClientHeader); type_name_generic!(LightClientHeaderDeneb, "LightClientHeader"); type_name_generic!(LightClientHeaderCapella, "LightClientHeader"); type_name_generic!(LightClientHeaderAltair, "LightClientHeader"); type_name_generic!(LightClientOptimisticUpdate); +type_name_generic!(LightClientOptimisticUpdateAltair, "LightClientOptimisticUpdate"); +type_name_generic!(LightClientOptimisticUpdateCapella, "LightClientOptimisticUpdate"); +type_name_generic!(LightClientOptimisticUpdateDeneb, "LightClientOptimisticUpdate"); type_name_generic!(LightClientUpdate); +type_name_generic!(LightClientUpdateAltair, "LightClientUpdate"); +type_name_generic!(LightClientUpdateCapella, "LightClientUpdate"); +type_name_generic!(LightClientUpdateDeneb, "LightClientUpdate"); type_name_generic!(PendingAttestation); type_name!(ProposerSlashing); type_name_generic!(SignedAggregateAndProof); diff --git a/testing/ef_tests/tests/tests.rs b/testing/ef_tests/tests/tests.rs index 2791adb3826..c81819f3dad 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -217,7 +217,7 @@ mod ssz_static { use ef_tests::{Handler, SszStaticHandler, SszStaticTHCHandler, SszStaticWithSpecHandler}; use types::blob_sidecar::BlobIdentifier; use types::historical_summary::HistoricalSummary; - use types::{LightClientHeaderAltair, *}; + use types::{LightClientBootstrapAltair, *}; ssz_static_test!(aggregate_and_proof, AggregateAndProof<_>); ssz_static_test!(attestation, Attestation<_>); @@ -248,11 +248,7 @@ mod ssz_static { ssz_static_test!(signed_voluntary_exit, SignedVoluntaryExit); ssz_static_test!(signing_data, SigningData); ssz_static_test!(validator, Validator); - // ssz_static_test!(voluntary_exit, VoluntaryExit); - // ssz_static_test!(light_client_bootstrap, SszStaticWithSpecHandler, LightClientBootstrap<_>); - // ssz_static_test!(light_client_update, SszStaticWithSpecHandler, LightClientUpdate<_>); - // ssz_static_test!(light_client_finality_update, SszStaticWithSpecHandler, LightClientFinalityUpdate<_>); - // ssz_static_test!(light_client_optimistic_update, SszStaticWithSpecHandler, LightClientOptimisticUpdate<_>); + ssz_static_test!(voluntary_exit, VoluntaryExit); // BeaconBlockBody has no internal indicator of which fork it is for, so we test it separately. #[test] fn beacon_block_body() { @@ -285,44 +281,38 @@ mod ssz_static { .run(); } + // LightClientBootstrap has no internal indicator of which fork it is for, so we test it separately. #[test] fn light_client_bootstrap() { - // SszStaticWithSpecHandler::, MinimalEthSpec>::altair_only() - // .run(); - // SszStaticWithSpecHandler::, MainnetEthSpec>::altair_only() - // .run(); - SszStaticWithSpecHandler::, MinimalEthSpec>::capella_only() + SszStaticHandler::, MinimalEthSpec>::altair_only() .run(); - SszStaticWithSpecHandler::, MainnetEthSpec>::capella_only() + SszStaticHandler::, MainnetEthSpec>::altair_only() .run(); - SszStaticWithSpecHandler::, MinimalEthSpec>::deneb_only() + SszStaticHandler::, MinimalEthSpec>::merge_only() .run(); - SszStaticWithSpecHandler::, MainnetEthSpec>::deneb_only() + SszStaticHandler::, MainnetEthSpec>::merge_only() .run(); - } - - #[test] - fn light_client_finality_update() { - // SszStaticWithSpecHandler::, MinimalEthSpec>::altair_only() - // .run(); - // SszStaticWithSpecHandler::, MainnetEthSpec>::altair_only() - // .run(); - SszStaticWithSpecHandler::, MinimalEthSpec>::capella_only() + SszStaticHandler::, MinimalEthSpec>::capella_only() .run(); - SszStaticWithSpecHandler::, MainnetEthSpec>::capella_only() + SszStaticHandler::, MainnetEthSpec>::capella_only() .run(); - SszStaticWithSpecHandler::, MinimalEthSpec>::deneb_only() + SszStaticHandler::, MinimalEthSpec>::deneb_only() .run(); - SszStaticWithSpecHandler::, MainnetEthSpec>::deneb_only() + SszStaticHandler::, MainnetEthSpec>::deneb_only() .run(); } + // LightClientHeader has no internal indicator of which fork it is for, so we test it separately. #[test] fn light_client_header() { SszStaticHandler::, MinimalEthSpec>::altair_only() .run(); SszStaticHandler::, MainnetEthSpec>::altair_only() .run(); + SszStaticHandler::, MinimalEthSpec>::merge_only() + .run(); + SszStaticHandler::, MainnetEthSpec>::merge_only() + .run(); SszStaticHandler::, MinimalEthSpec>::capella_only( ) @@ -337,36 +327,85 @@ mod ssz_static { .run(); } + // LightClientOptimisticUpdate has no internal indicator of which fork it is for, so we test it separately. #[test] fn light_client_optimistic_update() { - // SszStaticWithSpecHandler::, MinimalEthSpec>::altair_only( - // ) - // .run(); - // SszStaticWithSpecHandler::, MainnetEthSpec>::altair_only( - // ) - // .run(); - SszStaticWithSpecHandler::, MinimalEthSpec>::capella_only( + SszStaticHandler::, MinimalEthSpec>::altair_only( + ) + .run(); + SszStaticHandler::, MainnetEthSpec>::altair_only( + ) + .run(); + SszStaticHandler::, MinimalEthSpec>::merge_only( + ) + .run(); + SszStaticHandler::, MainnetEthSpec>::merge_only( + ) + .run(); + SszStaticHandler::, MinimalEthSpec>::capella_only( + ) + .run(); + SszStaticHandler::, MainnetEthSpec>::capella_only( + ) + .run(); + SszStaticHandler::, MinimalEthSpec>::deneb_only( + ) + .run(); + SszStaticHandler::, MainnetEthSpec>::deneb_only( + ) + .run(); + } + + // LightClientFinalityUpdate has no internal indicator of which fork it is for, so we test it separately. + #[test] + fn light_client_finality_update() { + SszStaticHandler::, MinimalEthSpec>::altair_only( + ) + .run(); + SszStaticHandler::, MainnetEthSpec>::altair_only( + ) + .run(); + SszStaticHandler::, MinimalEthSpec>::merge_only( + ) + .run(); + SszStaticHandler::, MainnetEthSpec>::merge_only( + ) + .run(); + SszStaticHandler::, MinimalEthSpec>::capella_only( ) .run(); - SszStaticWithSpecHandler::, MainnetEthSpec>::capella_only( + SszStaticHandler::, MainnetEthSpec>::capella_only( ) .run(); - SszStaticWithSpecHandler::, MinimalEthSpec>::deneb_only( + SszStaticHandler::, MinimalEthSpec>::deneb_only( ) .run(); - SszStaticWithSpecHandler::, MainnetEthSpec>::deneb_only( + SszStaticHandler::, MainnetEthSpec>::deneb_only( ) .run(); } + // LightClientUpdate has no internal indicator of which fork it is for, so we test it separately. #[test] fn light_client_update() { - // SszStaticWithSpecHandler::, MinimalEthSpec>::altair_only().run(); - // SszStaticWithSpecHandler::, MainnetEthSpec>::altair_only().run(); - SszStaticWithSpecHandler::, MinimalEthSpec>::capella_only().run(); - SszStaticWithSpecHandler::, MainnetEthSpec>::capella_only().run(); - SszStaticWithSpecHandler::, MinimalEthSpec>::deneb_only().run(); - SszStaticWithSpecHandler::, MainnetEthSpec>::deneb_only().run(); + SszStaticHandler::, MinimalEthSpec>::altair_only() + .run(); + SszStaticHandler::, MainnetEthSpec>::altair_only() + .run(); + SszStaticHandler::, MinimalEthSpec>::merge_only() + .run(); + SszStaticHandler::, MainnetEthSpec>::merge_only() + .run(); + SszStaticHandler::, MinimalEthSpec>::capella_only( + ) + .run(); + SszStaticHandler::, MainnetEthSpec>::capella_only( + ) + .run(); + SszStaticHandler::, MinimalEthSpec>::deneb_only() + .run(); + SszStaticHandler::, MainnetEthSpec>::deneb_only() + .run(); } #[test] diff --git a/testing/simulator/src/checks.rs b/testing/simulator/src/checks.rs index f38eacc394a..d30e44a1174 100644 --- a/testing/simulator/src/checks.rs +++ b/testing/simulator/src/checks.rs @@ -287,13 +287,13 @@ pub(crate) async fn verify_light_client_updates( } // Verify light client optimistic update. `signature_slot_distance` should be 1 in the ideal scenario. - let signature_slot = client + let signature_slot = *client .get_beacon_light_client_optimistic_update::() .await .map_err(|e| format!("Error while getting light client updates: {:?}", e))? .ok_or(format!("Light client optimistic update not found {slot:?}"))? .data - .signature_slot; + .signature_slot(); let signature_slot_distance = slot - signature_slot; if signature_slot_distance > light_client_update_slot_tolerance { return Err(format!("Existing optimistic update too old: signature slot {signature_slot}, current slot {slot:?}")); @@ -316,13 +316,13 @@ pub(crate) async fn verify_light_client_updates( } continue; } - let signature_slot = client + let signature_slot = *client .get_beacon_light_client_finality_update::() .await .map_err(|e| format!("Error while getting light client updates: {:?}", e))? .ok_or(format!("Light client finality update not found {slot:?}"))? .data - .signature_slot; + .signature_slot(); let signature_slot_distance = slot - signature_slot; if signature_slot_distance > light_client_update_slot_tolerance { return Err(format!( From dd43c1251a11a2dff9cdec13f2d4b887423393e9 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Tue, 12 Mar 2024 11:01:40 +0200 Subject: [PATCH 67/72] changes from feedback --- .../src/light_client_server_cache.rs | 8 ++- .../src/rpc/codec/ssz_snappy.rs | 2 +- consensus/types/src/light_client_bootstrap.rs | 24 ++++----- .../types/src/light_client_finality_update.rs | 17 ++----- consensus/types/src/light_client_header.rs | 4 +- .../src/light_client_optimistic_update.rs | 19 +++---- consensus/types/src/light_client_update.rs | 7 --- testing/ef_tests/src/cases/ssz_static.rs | 49 +------------------ testing/ef_tests/src/type_name.rs | 20 ++++++-- testing/ef_tests/tests/tests.rs | 22 +++++---- 10 files changed, 58 insertions(+), 114 deletions(-) diff --git a/beacon_node/beacon_chain/src/light_client_server_cache.rs b/beacon_node/beacon_chain/src/light_client_server_cache.rs index 325f06da44c..d480a6b56b2 100644 --- a/beacon_node/beacon_chain/src/light_client_server_cache.rs +++ b/beacon_node/beacon_chain/src/light_client_server_cache.rs @@ -101,8 +101,6 @@ impl LightClientServerCache { let attested_slot = attested_block.slot(); - let fork_name = attested_block.fork_name(chain_spec)?; - // Spec: Full nodes SHOULD provide the LightClientOptimisticUpdate with the highest // attested_header.beacon.slot (if multiple, highest signature_slot) as selected by fork choice let is_latest_optimistic = match &self.latest_optimistic_update.read().clone() { @@ -117,7 +115,7 @@ impl LightClientServerCache { &attested_block, sync_aggregate.clone(), signature_slot, - fork_name, + chain_spec, )?); }; @@ -140,7 +138,7 @@ impl LightClientServerCache { cached_parts.finality_branch.clone(), sync_aggregate.clone(), signature_slot, - fork_name, + chain_spec, )?); } else { debug!( @@ -227,7 +225,7 @@ fn is_latest_finality_update( attested_slot: Slot, signature_slot: Slot, ) -> bool { - let prev_slot = prev.get_slot(); + let prev_slot = prev.get_attested_header_slot(); if attested_slot > prev_slot { true } else { diff --git a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs index bdd566cce6b..c7752098604 100644 --- a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs +++ b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs @@ -592,7 +592,7 @@ fn handle_rpc_response( )))), SupportedProtocol::LightClientBootstrapV1 => match fork_name { Some(fork_name) => Ok(Some(RPCResponse::LightClientBootstrap(Arc::new( - LightClientBootstrap::from_ssz_bytes_for_fork(decoded_buffer, fork_name)?, + LightClientBootstrap::from_ssz_bytes(decoded_buffer, fork_name)?, )))), None => Err(RPCError::ErrorResponse( RPCResponseErrorCode::InvalidRequest, diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 1a3e2049c32..9966b5b90ea 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -90,14 +90,6 @@ impl LightClientBootstrap { Ok(bootstrap) } - /// Custom SSZ decoder that takes a `ForkName` as context. - pub fn from_ssz_bytes_for_fork( - bytes: &[u8], - fork_name: ForkName, - ) -> Result { - Self::from_ssz_bytes(bytes, fork_name) - } - pub fn from_beacon_state( beacon_state: &mut BeaconState, block: &SignedBeaconBlock, @@ -106,7 +98,9 @@ impl LightClientBootstrap { let mut header = beacon_state.latest_block_header().clone(); header.state_root = beacon_state.update_tree_hash_cache()?; let current_sync_committee_branch = - beacon_state.compute_merkle_proof(CURRENT_SYNC_COMMITTEE_INDEX)?; + FixedVector::new(beacon_state.compute_merkle_proof(CURRENT_SYNC_COMMITTEE_INDEX)?)?; + + let current_sync_committee = beacon_state.current_sync_committee()?.clone(); let light_client_bootstrap = match block .fork_name(chain_spec) @@ -115,18 +109,18 @@ impl LightClientBootstrap { ForkName::Base => return Err(Error::AltairForkNotActive), ForkName::Altair | ForkName::Merge => Self::Altair(LightClientBootstrapAltair { header: LightClientHeaderAltair::block_to_light_client_header(block)?, - current_sync_committee: beacon_state.current_sync_committee()?.clone(), - current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, + current_sync_committee, + current_sync_committee_branch, }), ForkName::Capella => Self::Capella(LightClientBootstrapCapella { header: LightClientHeaderCapella::block_to_light_client_header(block)?, - current_sync_committee: beacon_state.current_sync_committee()?.clone(), - current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, + current_sync_committee, + current_sync_committee_branch, }), ForkName::Deneb => Self::Deneb(LightClientBootstrapDeneb { header: LightClientHeaderDeneb::block_to_light_client_header(block)?, - current_sync_committee: beacon_state.current_sync_committee()?.clone(), - current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, + current_sync_committee, + current_sync_committee_branch, }), }; diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index bdd23c453b4..ace03821750 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -1,4 +1,5 @@ use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate}; +use crate::ChainSpec; use crate::{ light_client_update::*, test_utils::TestRandom, ForkName, ForkVersionDeserialize, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, SignedBeaconBlock, @@ -71,9 +72,9 @@ impl LightClientFinalityUpdate { finality_branch: FixedVector, sync_aggregate: SyncAggregate, signature_slot: Slot, - fork_name: ForkName, + chain_spec: &ChainSpec, ) -> Result { - let finality_update = match fork_name { + let finality_update = match attested_block.fork_name(chain_spec).map_err(|_| Error::InconsistentFork)? { ForkName::Altair | ForkName::Merge => { let finality_update = LightClientFinalityUpdateAltair { attested_header: LightClientHeaderAltair::block_to_light_client_header( @@ -122,7 +123,7 @@ impl LightClientFinalityUpdate { Ok(finality_update) } - pub fn get_slot<'a>(&'a self) -> Slot { + pub fn get_attested_header_slot<'a>(&'a self) -> Slot { map_light_client_finality_update_ref!(&'a _, self.to_ref(), |inner, cons| { cons(inner); inner.attested_header.beacon.slot @@ -152,14 +153,6 @@ impl LightClientFinalityUpdate { Ok(finality_update) } - - /// Custom SSZ decoder that takes a `ForkName` as context. - pub fn from_ssz_bytes_for_fork( - bytes: &[u8], - fork_name: ForkName, - ) -> Result { - Self::from_ssz_bytes(bytes, fork_name) - } } impl ForkVersionDeserialize for LightClientFinalityUpdate { @@ -178,4 +171,4 @@ impl ForkVersionDeserialize for LightClientFinalityUpdate { ))), } } -} \ No newline at end of file +} diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 6f49fed5cbb..8e8e07e8a91 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -37,7 +37,9 @@ use tree_hash_derive::TreeHash; arbitrary(bound = "E: EthSpec"), ) )] -#[derive(Debug, Clone, Serialize, TreeHash, Encode, Deserialize, arbitrary::Arbitrary, PartialEq)] +#[derive( + Debug, Clone, Serialize, TreeHash, Encode, Deserialize, arbitrary::Arbitrary, PartialEq, +)] #[serde(untagged)] #[tree_hash(enum_behaviour = "transparent")] #[ssz(enum_behaviour = "transparent")] diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index 9206fedc193..4a7a08b9c59 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -1,7 +1,7 @@ use super::{EthSpec, ForkName, ForkVersionDeserialize, Slot, SyncAggregate}; use crate::test_utils::TestRandom; use crate::{ - light_client_update::*, LightClientHeaderAltair, LightClientHeaderCapella, + light_client_update::*, ChainSpec, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, SignedBeaconBlock, }; use derivative::Derivative; @@ -64,9 +64,12 @@ impl LightClientOptimisticUpdate { attested_block: &SignedBeaconBlock, sync_aggregate: SyncAggregate, signature_slot: Slot, - fork_name: ForkName, + chain_spec: &ChainSpec, ) -> Result { - let optimistic_update = match fork_name { + let optimistic_update = match attested_block + .fork_name(chain_spec) + .map_err(|_| Error::InconsistentFork)? + { ForkName::Altair | ForkName::Merge => { let optimistic_update = LightClientOptimisticUpdateAltair { attested_header: LightClientHeaderAltair::block_to_light_client_header( @@ -147,14 +150,6 @@ impl LightClientOptimisticUpdate { Ok(optimistic_update) } - - /// Custom SSZ decoder that takes a `ForkName` as context. - pub fn from_ssz_bytes_for_fork( - bytes: &[u8], - fork_name: ForkName, - ) -> Result { - Self::from_ssz_bytes(bytes, fork_name) - } } impl ForkVersionDeserialize for LightClientOptimisticUpdate { @@ -173,4 +168,4 @@ impl ForkVersionDeserialize for LightClientOptimisticUpdate { ))), } } -} \ No newline at end of file +} diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 32b1fe76ecf..809b8ab8dac 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -259,13 +259,6 @@ impl LightClientUpdate { Ok(update) } - - pub fn from_ssz_bytes_for_fork( - bytes: &[u8], - fork_name: ForkName, - ) -> Result { - Self::from_ssz_bytes(bytes, fork_name) - } } #[cfg(test)] diff --git a/testing/ef_tests/src/cases/ssz_static.rs b/testing/ef_tests/src/cases/ssz_static.rs index 137410d6986..423dc31528f 100644 --- a/testing/ef_tests/src/cases/ssz_static.rs +++ b/testing/ef_tests/src/cases/ssz_static.rs @@ -5,10 +5,7 @@ use crate::decode::{snappy_decode_file, yaml_decode_file}; use serde::Deserialize; use ssz::Decode; use tree_hash::TreeHash; -use types::{ - BeaconBlock, BeaconState, ForkName, Hash256, LightClientBootstrap, LightClientFinalityUpdate, - LightClientOptimisticUpdate, LightClientUpdate, SignedBeaconBlock, -}; +use types::{BeaconBlock, BeaconState, ForkName, Hash256, SignedBeaconBlock}; #[derive(Debug, Clone, Deserialize)] struct SszStaticRoots { @@ -151,47 +148,3 @@ impl Case for SszStaticWithSpec> { Ok(()) } } - -impl Case for SszStaticWithSpec> { - fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> { - check_serialization(&self.value, &self.serialized, |bytes| { - LightClientBootstrap::from_ssz_bytes(bytes, fork_name) - })?; - check_tree_hash(&self.roots.root, self.value.tree_hash_root().as_bytes())?; - Ok(()) - } -} - -impl Case for SszStaticWithSpec> { - fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> { - check_serialization(&self.value, &self.serialized, |bytes| { - LightClientFinalityUpdate::from_ssz_bytes(bytes, fork_name) - })?; - check_tree_hash(&self.roots.root, self.value.tree_hash_root().as_bytes())?; - Ok(()) - } -} - -impl Case for SszStaticWithSpec> { - fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> { - check_serialization(&self.value, &self.serialized, |bytes| { - LightClientOptimisticUpdate::from_ssz_bytes(bytes, fork_name) - })?; - check_tree_hash(&self.roots.root, self.value.tree_hash_root().as_bytes())?; - Ok(()) - } -} - -impl Case for SszStaticWithSpec> { - fn result(&self, _case_index: usize, fork_name: ForkName) -> Result<(), Error> { - match fork_name { - ForkName::Base | ForkName::Merge => return Ok(()), - _ => {} - }; - check_serialization(&self.value, &self.serialized, |bytes| { - LightClientUpdate::from_ssz_bytes(bytes, fork_name) - })?; - check_tree_hash(&self.roots.root, self.value.tree_hash_root().as_bytes())?; - Ok(()) - } -} diff --git a/testing/ef_tests/src/type_name.rs b/testing/ef_tests/src/type_name.rs index 947f88ef964..ef5d7eb001c 100644 --- a/testing/ef_tests/src/type_name.rs +++ b/testing/ef_tests/src/type_name.rs @@ -79,16 +79,28 @@ type_name_generic!(LightClientBootstrapCapella, "LightClientBootstrap"); type_name_generic!(LightClientBootstrapDeneb, "LightClientBootstrap"); type_name_generic!(LightClientFinalityUpdate); type_name_generic!(LightClientFinalityUpdateAltair, "LightClientFinalityUpdate"); -type_name_generic!(LightClientFinalityUpdateCapella, "LightClientFinalityUpdate"); +type_name_generic!( + LightClientFinalityUpdateCapella, + "LightClientFinalityUpdate" +); type_name_generic!(LightClientFinalityUpdateDeneb, "LightClientFinalityUpdate"); type_name_generic!(LightClientHeader); type_name_generic!(LightClientHeaderDeneb, "LightClientHeader"); type_name_generic!(LightClientHeaderCapella, "LightClientHeader"); type_name_generic!(LightClientHeaderAltair, "LightClientHeader"); type_name_generic!(LightClientOptimisticUpdate); -type_name_generic!(LightClientOptimisticUpdateAltair, "LightClientOptimisticUpdate"); -type_name_generic!(LightClientOptimisticUpdateCapella, "LightClientOptimisticUpdate"); -type_name_generic!(LightClientOptimisticUpdateDeneb, "LightClientOptimisticUpdate"); +type_name_generic!( + LightClientOptimisticUpdateAltair, + "LightClientOptimisticUpdate" +); +type_name_generic!( + LightClientOptimisticUpdateCapella, + "LightClientOptimisticUpdate" +); +type_name_generic!( + LightClientOptimisticUpdateDeneb, + "LightClientOptimisticUpdate" +); type_name_generic!(LightClientUpdate); type_name_generic!(LightClientUpdateAltair, "LightClientUpdate"); type_name_generic!(LightClientUpdateCapella, "LightClientUpdate"); diff --git a/testing/ef_tests/tests/tests.rs b/testing/ef_tests/tests/tests.rs index c81819f3dad..3093239f7fd 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -275,23 +275,27 @@ mod ssz_static { // Altair and later #[test] fn contribution_and_proof() { - SszStaticHandler::, MinimalEthSpec>::altair_only() - .run(); - SszStaticHandler::, MainnetEthSpec>::altair_only() - .run(); + SszStaticHandler::, MinimalEthSpec>::altair_and_later( + ) + .run(); + SszStaticHandler::, MainnetEthSpec>::altair_and_later( + ) + .run(); } - // LightClientBootstrap has no internal indicator of which fork it is for, so we test it separately. + // LightClientBootstrap has no internal indicator of which fork it is for, so we test it separately. #[test] fn light_client_bootstrap() { SszStaticHandler::, MinimalEthSpec>::altair_only() .run(); SszStaticHandler::, MainnetEthSpec>::altair_only() .run(); - SszStaticHandler::, MinimalEthSpec>::merge_only() - .run(); - SszStaticHandler::, MainnetEthSpec>::merge_only() - .run(); + SszStaticHandler::, MinimalEthSpec>::merge_only( + ) + .run(); + SszStaticHandler::, MainnetEthSpec>::merge_only( + ) + .run(); SszStaticHandler::, MinimalEthSpec>::capella_only() .run(); SszStaticHandler::, MainnetEthSpec>::capella_only() From aba14758bf5c7b3a879c900ec452ff12fa522023 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Tue, 12 Mar 2024 11:29:29 +0200 Subject: [PATCH 68/72] linting --- consensus/types/src/beacon_block_body.rs | 5 +---- .../types/src/light_client_finality_update.rs | 5 ++++- consensus/types/src/light_client_header.rs | 14 ++++++-------- testing/ef_tests/check_all_files_accessed.py | 4 ++++ 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index 3da072debfc..cdac9bbf506 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -601,10 +601,7 @@ impl From>> } impl BeaconBlockBody { - pub fn block_body_merkle_proof( - &mut self, - generalized_index: usize, - ) -> Result, Error> { + pub fn block_body_merkle_proof(&self, generalized_index: usize) -> Result, Error> { let field_index = match generalized_index { light_client_update::EXECUTION_PAYLOAD_INDEX => { // Execution payload is a top-level field, subtract off the generalized indices diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index ace03821750..469a6cce3db 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -74,7 +74,10 @@ impl LightClientFinalityUpdate { signature_slot: Slot, chain_spec: &ChainSpec, ) -> Result { - let finality_update = match attested_block.fork_name(chain_spec).map_err(|_| Error::InconsistentFork)? { + let finality_update = match attested_block + .fork_name(chain_spec) + .map_err(|_| Error::InconsistentFork)? + { ForkName::Altair | ForkName::Merge => { let finality_update = LightClientFinalityUpdateAltair { attested_header: LightClientHeaderAltair::block_to_light_client_header( diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 8e8e07e8a91..ce640b94ab6 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -136,11 +136,10 @@ impl LightClientHeaderCapella { let payload = block .message() .execution_payload()? - .execution_payload_capella()? - .to_owned(); + .execution_payload_capella()?; - let header = ExecutionPayloadHeaderCapella::from(&payload); - let mut beacon_block_body = BeaconBlockBody::from( + let header = ExecutionPayloadHeaderCapella::from(payload); + let beacon_block_body = BeaconBlockBody::from( block .message() .body_capella() @@ -165,11 +164,10 @@ impl LightClientHeaderDeneb { let payload = block .message() .execution_payload()? - .execution_payload_deneb()? - .to_owned(); + .execution_payload_deneb()?; - let header = ExecutionPayloadHeaderDeneb::from(&payload); - let mut beacon_block_body = BeaconBlockBody::from( + let header = ExecutionPayloadHeaderDeneb::from(payload); + let beacon_block_body = BeaconBlockBody::from( block .message() .body_deneb() diff --git a/testing/ef_tests/check_all_files_accessed.py b/testing/ef_tests/check_all_files_accessed.py index 68e1fce4141..7629d61827f 100755 --- a/testing/ef_tests/check_all_files_accessed.py +++ b/testing/ef_tests/check_all_files_accessed.py @@ -27,6 +27,10 @@ "tests/.*/.*/ssz_static/PowBlock/", # light_client "tests/.*/.*/light_client", + # LightClientStore + "tests/.*/.*/ssz_static/LightClientStore", + # LightClientSnapshot + "tests/.*/.*/ssz_static/LightClientSnapshot", # One of the EF researchers likes to pack the tarballs on a Mac ".*\.DS_Store.*", # More Mac weirdness. From b3f7b81d14cdf4f4baf95cbe36794468c7e8d595 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Tue, 12 Mar 2024 16:32:40 +0200 Subject: [PATCH 69/72] test fix --- beacon_node/http_api/tests/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 39f2b9d48c1..ae109f93241 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -1717,7 +1717,7 @@ impl ApiTester { }; let expected = block.slot(); - assert_eq!(result.header.beacon().slot, expected); + assert_eq!(result.get_slot(), expected); self } From 8a7b2bc3363bca090320eeec88fdd0d3a27ba1dd Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Tue, 12 Mar 2024 19:55:47 +0200 Subject: [PATCH 70/72] cleanup --- consensus/types/src/light_client_bootstrap.rs | 8 ++++++++ .../types/src/light_client_finality_update.rs | 8 ++++++++ .../src/light_client_optimistic_update.rs | 8 ++++++++ consensus/types/src/light_client_update.rs | 3 +++ consensus/types/src/test_utils/macros.rs | 19 ------------------- consensus/types/src/test_utils/test_random.rs | 1 - .../test_random/light_client_header.rs | 10 ---------- 7 files changed, 27 insertions(+), 30 deletions(-) delete mode 100644 consensus/types/src/test_utils/test_random/light_client_header.rs diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 9966b5b90ea..d4e85a351d5 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -145,3 +145,11 @@ impl ForkVersionDeserialize for LightClientBootstrap { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::MainnetEthSpec; + + ssz_tests!(LightClientBootstrapDeneb); +} diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index 469a6cce3db..247ec87cde1 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -175,3 +175,11 @@ impl ForkVersionDeserialize for LightClientFinalityUpdate { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::MainnetEthSpec; + + ssz_tests!(LightClientFinalityUpdateDeneb); +} diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index 4a7a08b9c59..88f287d753e 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -169,3 +169,11 @@ impl ForkVersionDeserialize for LightClientOptimisticUpdate { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::MainnetEthSpec; + + ssz_tests!(LightClientOptimisticUpdateDeneb); +} diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 809b8ab8dac..09cc1950990 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -264,8 +264,11 @@ impl LightClientUpdate { #[cfg(test)] mod tests { use super::*; + use crate::MainnetEthSpec; use ssz_types::typenum::Unsigned; + ssz_tests!(LightClientUpdateDeneb); + #[test] fn finalized_root_params() { assert!(2usize.pow(FINALIZED_ROOT_PROOF_LEN as u32) <= FINALIZED_ROOT_INDEX); diff --git a/consensus/types/src/test_utils/macros.rs b/consensus/types/src/test_utils/macros.rs index c4615434aa2..4fd7720689d 100644 --- a/consensus/types/src/test_utils/macros.rs +++ b/consensus/types/src/test_utils/macros.rs @@ -27,25 +27,6 @@ macro_rules! ssz_tests { }; } -#[macro_export] -macro_rules! ssz_tests_by_fork { - ($type: ty, $fork:expr) => { - #[test] - pub fn test_ssz_round_trip() { - use ssz::ssz_encode; - use $crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = <$type>::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let decoded = <$type>::from_ssz_bytes(&bytes, $fork).unwrap(); - - assert_eq!(original, decoded); - } - }; -} - #[macro_export] macro_rules! tree_hash_tests { ($type: ty) => { diff --git a/consensus/types/src/test_utils/test_random.rs b/consensus/types/src/test_utils/test_random.rs index 9881b694e7b..f31df2ce1b6 100644 --- a/consensus/types/src/test_utils/test_random.rs +++ b/consensus/types/src/test_utils/test_random.rs @@ -12,7 +12,6 @@ mod bitfield; mod hash256; mod kzg_commitment; mod kzg_proof; -mod light_client_header; mod public_key; mod public_key_bytes; mod secret_key; diff --git a/consensus/types/src/test_utils/test_random/light_client_header.rs b/consensus/types/src/test_utils/test_random/light_client_header.rs deleted file mode 100644 index 6a97aff0cd2..00000000000 --- a/consensus/types/src/test_utils/test_random/light_client_header.rs +++ /dev/null @@ -1,10 +0,0 @@ -use super::*; -use crate::{light_client_header::LightClientHeaderDeneb, LightClientHeader}; - -/// Implements `TestRandom` for the `LightClientHeader` superstruct -/// We choose `LightClientHeaderDeneb` since it is a superset of all other variants -impl TestRandom for LightClientHeader { - fn random_for_test(rng: &mut impl RngCore) -> Self { - LightClientHeaderDeneb::::random_for_test(rng).into() - } -} From d314d12a0f23b65df1d953e18778fdf59d327bfb Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Wed, 13 Mar 2024 15:01:40 +1100 Subject: [PATCH 71/72] Remove unused `derive`. --- consensus/types/src/beacon_block_body.rs | 3 +-- consensus/types/src/beacon_block_header.rs | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index cdac9bbf506..9524ceb6283 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -54,12 +54,11 @@ pub const BLOB_KZG_COMMITMENTS_INDEX: usize = 11; cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"), partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant") )] -#[derive(Debug, Clone, Serialize, Deserialize, Derivative, TreeHash, arbitrary::Arbitrary)] +#[derive(Debug, Clone, Serialize, Deserialize, Derivative, arbitrary::Arbitrary)] #[derivative(PartialEq, Hash(bound = "T: EthSpec"))] #[serde(untagged)] #[serde(bound = "T: EthSpec, Payload: AbstractExecPayload")] #[arbitrary(bound = "T: EthSpec, Payload: AbstractExecPayload")] -#[tree_hash(enum_behaviour = "transparent")] pub struct BeaconBlockBody = FullPayload> { pub randao_reveal: Signature, pub eth1_data: Eth1Data, diff --git a/consensus/types/src/beacon_block_header.rs b/consensus/types/src/beacon_block_header.rs index b5e4fd38aae..b382359313c 100644 --- a/consensus/types/src/beacon_block_header.rs +++ b/consensus/types/src/beacon_block_header.rs @@ -13,7 +13,6 @@ use tree_hash_derive::TreeHash; #[derive( arbitrary::Arbitrary, Debug, - Default, PartialEq, Eq, Hash, From a7dd9ee18caec422f6d9ed63b3b4dffeb63bbb33 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Sat, 23 Mar 2024 11:09:43 +0200 Subject: [PATCH 72/72] beta compiler fix --- consensus/types/src/light_client_header.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index ce640b94ab6..0ac3db6e038 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -9,7 +9,6 @@ use crate::{ }; use derivative::Derivative; use serde::{Deserialize, Serialize}; -use serde_json; use ssz::Decode; use ssz_derive::{Decode, Encode}; use std::marker::PhantomData;