diff --git a/.gitignore b/.gitignore index 60cc513..94678de 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .idea/ testnet/ .vscode/* +.nvim.lua diff --git a/finalizer/src/actor.rs b/finalizer/src/actor.rs index 5f42ca9..b90f606 100644 --- a/finalizer/src/actor.rs +++ b/finalizer/src/actor.rs @@ -220,6 +220,36 @@ impl< })) .await; + // Send initial forkchoice to the execution client so it knows the chain + // head and can start P2P sync. Then wait for sync to complete before + // replaying any blocks. Without this, catch-up blocks fail because the + // execution client doesn't have them yet. + { + let forkchoice = self.canonical_state.forkchoice; + if !forkchoice.head_block_hash.is_zero() { + info!( + head = %forkchoice.head_block_hash, + "sending initial forkchoice update to execution client, waiting for sync..." + ); + loop { + let status = self.engine_client.commit_hash_with_status(forkchoice).await; + if status.is_valid() { + info!("execution client synced to checkpoint head, ready to replay blocks"); + break; + } else if status.is_syncing() { + info!("execution client still syncing, waiting 5s..."); + self.context.sleep(std::time::Duration::from_secs(5)).await; + } else { + warn!( + ?status, + "unexpected response to initial forkchoice update, proceeding anyway" + ); + break; + } + } + } + } + loop { if self.validator_exit && is_first_block_of_epoch( diff --git a/finalizer/src/tests/mocks.rs b/finalizer/src/tests/mocks.rs index 54d5ab1..c622162 100644 --- a/finalizer/src/tests/mocks.rs +++ b/finalizer/src/tests/mocks.rs @@ -3,7 +3,8 @@ use alloy_primitives::{Address, FixedBytes, U256}; use alloy_rpc_types_engine::{ ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, ExecutionPayloadV1, ExecutionPayloadV2, - ExecutionPayloadV3, ForkchoiceState, PayloadId, PayloadStatus, PayloadStatusEnum, + ExecutionPayloadV3, ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus, + PayloadStatusEnum, }; use commonware_consensus::simplex::scheme::bls12381_multisig; use commonware_consensus::simplex::types::{Finalization, Finalize, Proposal}; @@ -136,6 +137,19 @@ impl EngineClient for MockEngineClient { } async fn commit_hash(&mut self, _fork_choice_state: ForkchoiceState) {} + + async fn commit_hash_with_status( + &mut self, + _fork_choice_state: ForkchoiceState, + ) -> ForkchoiceUpdated { + ForkchoiceUpdated { + payload_status: PayloadStatus { + status: PayloadStatusEnum::Valid, + latest_valid_hash: Some([0u8; 32].into()), + }, + payload_id: None, + } + } } /// Minimal mock NetworkOracle diff --git a/node/src/engine.rs b/node/src/engine.rs index 1cdf0aa..2a7366f 100644 --- a/node/src/engine.rs +++ b/node/src/engine.rs @@ -64,7 +64,7 @@ pub const BLOCKS_PER_EPOCH: u64 = 50; #[cfg(debug_assertions)] pub const BLOCKS_PER_EPOCH: u64 = 10; #[cfg(all(not(debug_assertions), not(feature = "e2e")))] -const BLOCKS_PER_EPOCH: u64 = 10000; +const BLOCKS_PER_EPOCH: u64 = 50; const VALIDATOR_MAX_WITHDRAWALS_PER_BLOCK: usize = 16; // diff --git a/node/src/test_harness/mock_engine_client.rs b/node/src/test_harness/mock_engine_client.rs index 2c7cf96..6ea89ef 100644 --- a/node/src/test_harness/mock_engine_client.rs +++ b/node/src/test_harness/mock_engine_client.rs @@ -4,8 +4,8 @@ use alloy_primitives::hex; use alloy_primitives::{Address, B256, Bloom, Bytes, FixedBytes, U256}; use alloy_rpc_types_engine::{ BlobsBundleV1, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, ExecutionPayloadV1, - ExecutionPayloadV2, ExecutionPayloadV3, ForkchoiceState, PayloadId, PayloadStatus, - PayloadStatusEnum, + ExecutionPayloadV2, ExecutionPayloadV3, ForkchoiceState, ForkchoiceUpdated, PayloadId, + PayloadStatus, PayloadStatusEnum, }; use rand::RngCore; use std::collections::HashMap; @@ -469,6 +469,17 @@ impl EngineClient for MockEngineClient { } } } + + async fn commit_hash_with_status( + &mut self, + fork_choice_state: ForkchoiceState, + ) -> ForkchoiceUpdated { + self.commit_hash(fork_choice_state).await; + ForkchoiceUpdated { + payload_status: PayloadStatus::new(PayloadStatusEnum::Valid, Some(fork_choice_state.head_block_hash)), + payload_id: None, + } + } } pub struct MockEngineNetworkBuilder { diff --git a/types/src/engine_client.rs b/types/src/engine_client.rs index 5b11c01..c2fc2e5 100644 --- a/types/src/engine_client.rs +++ b/types/src/engine_client.rs @@ -21,7 +21,8 @@ use alloy_eips::eip4895::Withdrawal; use alloy_primitives::{Address, FixedBytes}; use alloy_provider::{ProviderBuilder, RootProvider, ext::EngineApi}; use alloy_rpc_types_engine::{ - ExecutionPayloadEnvelopeV4, ForkchoiceState, PayloadAttributes, PayloadId, PayloadStatus, + ExecutionPayloadEnvelopeV4, ForkchoiceState, ForkchoiceUpdated, PayloadAttributes, PayloadId, + PayloadStatus, }; use tracing::{error, warn}; @@ -51,6 +52,11 @@ pub trait EngineClient: Clone + Send + Sync + 'static { &mut self, fork_choice_state: ForkchoiceState, ) -> impl Future + Send; + + fn commit_hash_with_status( + &mut self, + fork_choice_state: ForkchoiceState, + ) -> impl Future + Send; } #[derive(Clone)] @@ -192,6 +198,27 @@ impl EngineClient for RethEngineClient { Err(_) => panic!("Unable to get a response"), }; } + + async fn commit_hash_with_status( + &mut self, + fork_choice_state: ForkchoiceState, + ) -> ForkchoiceUpdated { + match self + .provider + .fork_choice_updated_v3(fork_choice_state, None) + .await + { + Ok(res) => res, + Err(e) if e.is_transport_error() => { + self.wait_until_reconnect_available().await; + self.provider + .fork_choice_updated_v3(fork_choice_state, None) + .await + .expect("Failed to commit hash after reconnect") + } + Err(_) => panic!("Unable to get a response"), + } + } } #[cfg(feature = "bad-blocks")] @@ -345,6 +372,27 @@ impl EngineClient for BadBlockEngineClient { Err(_) => panic!("Unable to get a response"), }; } + + async fn commit_hash_with_status( + &mut self, + fork_choice_state: ForkchoiceState, + ) -> ForkchoiceUpdated { + match self + .provider + .fork_choice_updated_v3(fork_choice_state, None) + .await + { + Ok(res) => res, + Err(e) if e.is_transport_error() => { + self.wait_until_reconnect_available().await; + self.provider + .fork_choice_updated_v3(fork_choice_state, None) + .await + .expect("Failed to commit hash after reconnect") + } + Err(_) => panic!("Unable to get a response"), + } + } } #[cfg(feature = "bench")] @@ -357,7 +405,7 @@ pub mod benchmarking { use alloy_provider::{ProviderBuilder, RootProvider, ext::EngineApi}; use alloy_rpc_types_engine::{ ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, ExecutionPayloadV3, - ForkchoiceState, PayloadId, PayloadStatus, + ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus, }; use alloy_transport_ipc::IpcConnect; use serde::{Deserialize, Serialize}; @@ -441,6 +489,16 @@ pub mod benchmarking { .await .unwrap(); } + + async fn commit_hash_with_status( + &mut self, + fork_choice_state: ForkchoiceState, + ) -> ForkchoiceUpdated { + self.provider + .fork_choice_updated_v3(fork_choice_state, None) + .await + .unwrap() + } } #[derive(Debug, Serialize, Deserialize)]