diff --git a/.gitignore b/.gitignore index 9e3c9ed72..f2ca5fd60 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ contracts/beefy-state.json go/ gocache/ +go.work* diff --git a/parachain/pallets/ethereum-client/fixtures/src/lib.rs b/parachain/pallets/ethereum-client/fixtures/src/lib.rs index facaffb81..f6d88e99d 100644 --- a/parachain/pallets/ethereum-client/fixtures/src/lib.rs +++ b/parachain/pallets/ethereum-client/fixtures/src/lib.rs @@ -1125,6 +1125,8 @@ pub fn make_sync_committee_update() -> Box { hex!("5340ad5877c72dca689ca04bc8fedb78d67a4801d99887937edd8ccd29f87e82").into(), hex!("f5ff4b0c6190005015889879568f5f0d9c40134c7ec4ffdda47950dcd92395ad").into(), ], + execution_header: None, + execution_branch: None, }) } @@ -1165,7 +1167,9 @@ pub fn make_finalized_header_update() -> Box { hex!("ecea7e1d3152d8130e83afdfe34b4de4ba2b69a33c9471991096daf454de9cf5").into(), hex!("b2bf1758e50b2bfff29169fbc70fdb884b2b05bb615dbc53567574da6f4f1ae2").into(), hex!("cd87069daf70975779126d6af833b7d636c75ca4d5e750ebcad0e76408a5e5bf").into(), - ] + ], + execution_header: None, + execution_branch: None, }) } diff --git a/parachain/pallets/ethereum-client/src/lib.rs b/parachain/pallets/ethereum-client/src/lib.rs index a54d4a05a..0facc4ab8 100644 --- a/parachain/pallets/ethereum-client/src/lib.rs +++ b/parachain/pallets/ethereum-client/src/lib.rs @@ -34,9 +34,7 @@ mod tests; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; -use frame_support::{ - dispatch::DispatchResult, pallet_prelude::OptionQuery, traits::Get, transactional, -}; +use frame_support::{dispatch::DispatchResult, pallet_prelude::OptionQuery, traits::Get}; use frame_system::ensure_signed; use primitives::{ fast_aggregate_verify, verify_merkle_branch, verify_receipt_proof, BeaconHeader, BlsError, @@ -214,8 +212,7 @@ pub mod pallet { #[pallet::call] impl Pallet { #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::force_checkpoint())] - #[transactional] + #[pallet::weight((T::WeightInfo::force_checkpoint(), DispatchClass::Operational))] /// Used for pallet initialization and light client resetting. Needs to be called by /// the root origin. pub fn force_checkpoint( @@ -234,7 +231,6 @@ pub mod pallet { Some(_) => T::WeightInfo::submit_with_sync_committee(), } })] - #[transactional] /// Submits a new finalized beacon header update. The update may contain the next /// sync committee. pub fn submit(origin: OriginFor, update: Box) -> DispatchResult { @@ -246,7 +242,6 @@ pub mod pallet { #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::submit_execution_header())] - #[transactional] /// Submits a new execution header update. The relevant related beacon header /// is also included to prove the execution header, as well as ancestry proof data. pub fn submit_execution_header( @@ -330,32 +325,11 @@ pub mod pallet { } pub(crate) fn process_update(update: &Update) -> DispatchResult { - Self::cross_check_execution_state()?; Self::verify_update(update)?; Self::apply_update(update)?; Ok(()) } - /// Cross check to make sure that execution header import does not fall too far behind - /// finalised beacon header import. If that happens just return an error and pause - /// processing until execution header processing has caught up. - pub(crate) fn cross_check_execution_state() -> DispatchResult { - let latest_finalized_state = - FinalizedBeaconState::::get(LatestFinalizedBlockRoot::::get()) - .ok_or(Error::::NotBootstrapped)?; - let latest_execution_state = Self::latest_execution_state(); - // The execution header import should be at least within the slot range of a sync - // committee period. - let max_latency = config::EPOCHS_PER_SYNC_COMMITTEE_PERIOD * config::SLOTS_PER_EPOCH; - ensure!( - latest_execution_state.beacon_slot == 0 || - latest_finalized_state.slot < - latest_execution_state.beacon_slot + max_latency as u64, - Error::::ExecutionHeaderTooFarBehind - ); - Ok(()) - } - /// References and strictly follows /// Verifies that provided next sync committee is valid through a series of checks /// (including checking that a sync committee period isn't skipped and that the header is @@ -479,6 +453,28 @@ pub mod pallet { ) .map_err(|e| Error::::BLSVerificationFailed(e))?; + // Execution payload header corresponding to `beacon.body_root` (from Capella onward) + // https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/light-client/sync-protocol.md#modified-lightclientheader + if let Some(version_execution_header) = &update.execution_header { + let execution_header_root: H256 = version_execution_header + .hash_tree_root() + .map_err(|_| Error::::BlockBodyHashTreeRootFailed)?; + ensure!( + &update.execution_branch.is_some(), + Error::::InvalidExecutionHeaderProof + ); + ensure!( + verify_merkle_branch( + execution_header_root, + &update.execution_branch.clone().unwrap(), + config::EXECUTION_HEADER_SUBTREE_INDEX, + config::EXECUTION_HEADER_DEPTH, + update.finalized_header.body_root + ), + Error::::InvalidExecutionHeaderProof + ); + } + Ok(()) } @@ -530,6 +526,19 @@ pub mod pallet { )?; } + if let Some(version_execution_header) = &update.execution_header { + let finalized_block_root: H256 = update + .finalized_header + .hash_tree_root() + .map_err(|_| Error::::HeaderHashTreeRootFailed)?; + Self::store_execution_header( + version_execution_header.block_hash(), + version_execution_header.clone().into(), + update.finalized_header.slot, + finalized_block_root, + ); + } + Ok(()) } @@ -548,15 +557,6 @@ pub mod pallet { Error::::HeaderNotFinalized ); - // Checks that we don't skip execution headers, they need to be imported sequentially. - let latest_execution_state: ExecutionHeaderState = Self::latest_execution_state(); - ensure!( - latest_execution_state.block_number == 0 || - update.execution_header.block_number() == - latest_execution_state.block_number + 1, - Error::::ExecutionHeaderSkippedBlock - ); - // Gets the hash tree root of the execution header, in preparation for the execution // header proof (used to check that the execution header is rooted in the beacon // header body. @@ -709,12 +709,15 @@ pub mod pallet { block_number ); - LatestExecutionState::::mutate(|s| { - s.beacon_block_root = beacon_block_root; - s.beacon_slot = beacon_slot; - s.block_hash = block_hash; - s.block_number = block_number; - }); + let latest_execution_state = LatestExecutionState::::get(); + if beacon_slot > latest_execution_state.beacon_slot { + LatestExecutionState::::mutate(|s| { + s.beacon_block_root = beacon_block_root; + s.beacon_slot = beacon_slot; + s.block_hash = block_hash; + s.block_number = block_number; + }); + } Self::deposit_event(Event::ExecutionHeaderImported { block_hash, block_number }); } diff --git a/parachain/pallets/ethereum-client/src/tests.rs b/parachain/pallets/ethereum-client/src/tests.rs index 50b6a25c3..e8a57d3bc 100644 --- a/parachain/pallets/ethereum-client/src/tests.rs +++ b/parachain/pallets/ethereum-client/src/tests.rs @@ -3,7 +3,7 @@ use crate::{ functions::compute_period, pallet::ExecutionHeaders, sync_committee_sum, verify_merkle_branch, BeaconHeader, CompactBeaconState, Error, ExecutionHeaderBuffer, FinalizedBeaconState, - LatestExecutionState, LatestFinalizedBlockRoot, NextSyncCommittee, SyncCommitteePrepared, + LatestFinalizedBlockRoot, NextSyncCommittee, SyncCommitteePrepared, }; use crate::mock::{ @@ -18,10 +18,7 @@ pub use crate::mock::*; use crate::config::{EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH}; use frame_support::{assert_err, assert_noop, assert_ok}; use hex_literal::hex; -use primitives::{ - CompactExecutionHeader, ExecutionHeaderState, Fork, ForkVersions, NextSyncCommitteeUpdate, - VersionedExecutionPayloadHeader, -}; +use primitives::{CompactExecutionHeader, Fork, ForkVersions, NextSyncCommitteeUpdate}; use rand::{thread_rng, Rng}; use snowbridge_core::{ inbound::{VerificationError, Verifier}, @@ -348,34 +345,6 @@ fn find_present_keys() { }); } -#[test] -fn cross_check_execution_state() { - new_tester().execute_with(|| { - let header_root: H256 = TEST_HASH.into(); - >::insert( - header_root, - CompactBeaconState { - // set slot to period 5 - slot: ((EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH) * 5) as u64, - block_roots_root: Default::default(), - }, - ); - LatestFinalizedBlockRoot::::set(header_root); - >::set(ExecutionHeaderState { - beacon_block_root: Default::default(), - // set slot to period 2 - beacon_slot: ((EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH) * 2) as u64, - block_hash: Default::default(), - block_number: 0, - }); - - assert_err!( - EthereumBeaconClient::cross_check_execution_state(), - Error::::ExecutionHeaderTooFarBehind - ); - }); -} - /* SYNC PROCESS TESTS */ #[test] @@ -608,40 +577,6 @@ fn submit_update_with_skipped_sync_committee_period() { }); } -#[test] -fn submit_update_execution_headers_too_far_behind() { - let checkpoint = Box::new(load_checkpoint_update_fixture()); - let finalized_header_update = Box::new(load_finalized_header_update_fixture()); - let execution_header_update = Box::new(load_execution_header_update_fixture()); - let next_update = Box::new(load_next_sync_committee_update_fixture()); - - new_tester().execute_with(|| { - let far_ahead_finalized_header_slot = finalized_header_update.finalized_header.slot + - (EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH * 2) as u64; - assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); - assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), finalized_header_update)); - assert_ok!(EthereumBeaconClient::submit_execution_header( - RuntimeOrigin::signed(1), - execution_header_update - )); - - let header_root: H256 = TEST_HASH.into(); - >::insert( - header_root, - CompactBeaconState { - slot: far_ahead_finalized_header_slot, - block_roots_root: Default::default(), - }, - ); - LatestFinalizedBlockRoot::::set(header_root); - - assert_err!( - EthereumBeaconClient::submit(RuntimeOrigin::signed(1), next_update), - Error::::ExecutionHeaderTooFarBehind - ); - }); -} - #[test] fn submit_irrelevant_update() { let checkpoint = Box::new(load_checkpoint_update_fixture()); @@ -764,51 +699,6 @@ fn submit_execution_header_update_invalid_execution_header_proof() { }); } -#[test] -fn submit_execution_header_update_that_skips_block() { - let checkpoint = Box::new(load_checkpoint_update_fixture()); - let finalized_header_update = Box::new(load_finalized_header_update_fixture()); - let execution_header_update = Box::new(load_execution_header_update_fixture()); - let mut skipped_block_execution_header_update = - Box::new(load_execution_header_update_fixture()); - let mut skipped_execution_header = - skipped_block_execution_header_update.execution_header.clone(); - - skipped_execution_header = match skipped_execution_header { - VersionedExecutionPayloadHeader::Capella(execution_payload_header) => { - let mut mut_execution_payload_header = execution_payload_header.clone(); - mut_execution_payload_header.block_number = execution_payload_header.block_number + 2; - VersionedExecutionPayloadHeader::Capella(mut_execution_payload_header) - }, - VersionedExecutionPayloadHeader::Deneb(execution_payload_header) => { - let mut mut_execution_payload_header = execution_payload_header.clone(); - mut_execution_payload_header.block_number = execution_payload_header.block_number + 2; - VersionedExecutionPayloadHeader::Deneb(mut_execution_payload_header) - }, - }; - - skipped_block_execution_header_update.execution_header = skipped_execution_header; - - new_tester().execute_with(|| { - assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); - assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), finalized_header_update)); - assert_ok!(EthereumBeaconClient::submit_execution_header( - RuntimeOrigin::signed(1), - execution_header_update.clone() - )); - assert!(>::contains_key( - execution_header_update.execution_header.block_hash() - )); - assert_err!( - EthereumBeaconClient::submit_execution_header( - RuntimeOrigin::signed(1), - skipped_block_execution_header_update - ), - Error::::ExecutionHeaderSkippedBlock - ); - }); -} - #[test] fn submit_execution_header_update_that_is_also_finalized_header_which_is_not_stored() { let checkpoint = Box::new(load_checkpoint_update_fixture()); diff --git a/parachain/primitives/beacon/src/types.rs b/parachain/primitives/beacon/src/types.rs index 6f0886ba8..deac8094c 100644 --- a/parachain/primitives/beacon/src/types.rs +++ b/parachain/primitives/beacon/src/types.rs @@ -450,6 +450,12 @@ impl VersionedExecutionPayloadHeader { } } +impl Default for VersionedExecutionPayloadHeader { + fn default() -> Self { + VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader::default()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/parachain/primitives/beacon/src/updates.rs b/parachain/primitives/beacon/src/updates.rs index 1ecd32c6d..7899c0470 100644 --- a/parachain/primitives/beacon/src/updates.rs +++ b/parachain/primitives/beacon/src/updates.rs @@ -8,7 +8,9 @@ use sp_std::prelude::*; use crate::types::{BeaconHeader, SyncAggregate, SyncCommittee, VersionedExecutionPayloadHeader}; -#[derive(Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo)] +#[derive( + Default, Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, +)] #[cfg_attr( feature = "std", derive(serde::Serialize, serde::Deserialize), @@ -23,26 +25,13 @@ pub struct CheckpointUpdate { pub block_roots_branch: Vec, } -impl Default for CheckpointUpdate { - fn default() -> Self { - CheckpointUpdate { - header: Default::default(), - current_sync_committee: Default::default(), - current_sync_committee_branch: Default::default(), - validators_root: Default::default(), - block_roots_root: Default::default(), - block_roots_branch: Default::default(), - } - } -} - #[derive( Default, Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, )] #[cfg_attr( feature = "std", derive(serde::Deserialize), - serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = "")) + serde(bound(serialize = ""), bound(deserialize = "")) )] pub struct Update { /// A recent header attesting to the finalized header, using its `state_root`. @@ -64,6 +53,10 @@ pub struct Update pub block_roots_root: H256, /// The merkle path to prove the `block_roots_root` value. pub block_roots_branch: Vec, + /// The execution header to be imported + pub execution_header: Option, + /// The merkle proof for the execution_header + pub execution_branch: Option>, } #[derive( diff --git a/relayer/chain/ethereum/header.go b/relayer/chain/ethereum/header.go index b3600a8fb..f9a562873 100644 --- a/relayer/chain/ethereum/header.go +++ b/relayer/chain/ethereum/header.go @@ -5,11 +5,12 @@ package ethereum import ( "fmt" + "math/big" + etypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" "github.com/snowfork/go-substrate-rpc-client/v4/scale" types "github.com/snowfork/go-substrate-rpc-client/v4/types" - "math/big" ) type HeaderID struct { diff --git a/relayer/chain/parachain/writer.go b/relayer/chain/parachain/writer.go index 8e35ff98a..973b4a480 100644 --- a/relayer/chain/parachain/writer.go +++ b/relayer/chain/parachain/writer.go @@ -385,3 +385,24 @@ func (wr *ParachainWriter) getNumberFromParachain(pallet, storage string) (uint6 return uint64(number), nil } + +func (wr *ParachainWriter) GetCompactExecutionHeaderStateByBlockHash(blockHash types.H256) (state.CompactExecutionHeaderState, error) { + var headerState state.CompactExecutionHeaderState + key, err := types.CreateStorageKey(wr.conn.Metadata(), "EthereumBeaconClient", "ExecutionHeaders", blockHash[:], nil) + if err != nil { + return headerState, fmt.Errorf("create storage key for ExecutionHeaders: %w", err) + } + + var compactExecutionHeader scale.CompactExecutionHeader + _, err = wr.conn.API().RPC.State.GetStorageLatest(key, &compactExecutionHeader) + if err != nil { + return headerState, fmt.Errorf("get storage for ExecutionHeaders (err): %w", err) + } + headerState = state.CompactExecutionHeaderState{ + ParentHash: common.Hash(compactExecutionHeader.ParentHash), + BlockNumber: uint64(compactExecutionHeader.BlockNumber.Int64()), + StateRoot: common.Hash(compactExecutionHeader.StateRoot), + ReceiptsRoot: common.Hash(compactExecutionHeader.ReceiptsRoot), + } + return headerState, nil +} diff --git a/relayer/cmd/generate_beacon_data.go b/relayer/cmd/generate_beacon_data.go index d1c1fd4c8..15e58439d 100644 --- a/relayer/cmd/generate_beacon_data.go +++ b/relayer/cmd/generate_beacon_data.go @@ -53,7 +53,7 @@ func generateBeaconCheckpointCmd() *cobra.Command { } cmd.Flags().String("url", "http://127.0.0.1:9596", "Beacon URL") - cmd.Flags().Bool("export_json", false, "Export Json") + cmd.Flags().Bool("export-json", false, "Export Json") return cmd } @@ -115,9 +115,9 @@ func generateBeaconCheckpoint(cmd *cobra.Command, _ []string) error { if err != nil { return fmt.Errorf("get initial sync: %w", err) } - exportJson, err := cmd.Flags().GetBool("export_json") + exportJson, err := cmd.Flags().GetBool("export-json") if err != nil { - return err + return fmt.Errorf("get initial sync: %w", err) } if exportJson { initialSync := checkPointScale.ToJSON() @@ -282,7 +282,7 @@ func generateBeaconTestFixture(cmd *cobra.Command, _ []string) error { BlockRootsTree: finalizedUpdateAfterMessage.BlockRootsTree, Slot: uint64(finalizedUpdateAfterMessage.Payload.FinalizedHeader.Slot), } - headerUpdateScale, err := s.GetNextHeaderUpdateBySlotWithCheckpoint(beaconBlockSlot, &checkPoint) + headerUpdateScale, err := s.GetHeaderUpdateBySlotWithCheckpoint(beaconBlockSlot, &checkPoint) if err != nil { return fmt.Errorf("get header update: %w", err) } diff --git a/relayer/relays/beacon/header/header.go b/relayer/relays/beacon/header/header.go index 1b1d56f90..a40c1841b 100644 --- a/relayer/relays/beacon/header/header.go +++ b/relayer/relays/beacon/header/header.go @@ -6,11 +6,12 @@ import ( "fmt" "time" - "github.com/snowfork/go-substrate-rpc-client/v4/types" + "github.com/ethereum/go-ethereum/common" "github.com/snowfork/snowbridge/relayer/relays/beacon/config" "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/scale" log "github.com/sirupsen/logrus" + "github.com/snowfork/go-substrate-rpc-client/v4/types" "github.com/snowfork/snowbridge/relayer/chain/parachain" "github.com/snowfork/snowbridge/relayer/relays/beacon/cache" "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer" @@ -186,11 +187,6 @@ func (h *Header) SyncFinalizedHeader(ctx context.Context) error { } func (h *Header) SyncHeaders(ctx context.Context) error { - err := h.SyncExecutionHeaders(ctx) - if err != nil { - return err - } - hasChanged, err := h.syncer.HasFinalizedHeaderChanged(h.cache.Finalized.LastSyncedHash) if err != nil { return err @@ -208,79 +204,12 @@ func (h *Header) SyncHeaders(ctx context.Context) error { return nil } -func (h *Header) SyncExecutionHeaders(ctx context.Context) error { - fromSlot := h.cache.LastSyncedExecutionSlot - // SyncExecutionHeaders at least from initial checkpoint - if fromSlot <= h.cache.InitialCheckpointSlot { - fromSlot = h.cache.InitialCheckpointSlot - } - toSlot := h.cache.Finalized.LastSyncedSlot - if fromSlot >= toSlot { - log.WithFields(log.Fields{ - "fromSlot": fromSlot, - "toSlot": toSlot, - }).Info("execution headers sync up to date with last finalized header") - return nil - } - log.WithFields(log.Fields{ - "fromSlot": fromSlot, - "fromEpoch": h.syncer.ComputeEpochAtSlot(fromSlot), - "toSlot": toSlot, - "toEpoch": h.syncer.ComputeEpochAtSlot(toSlot), - "totalSlots": toSlot - fromSlot, - }).Info("starting to back-fill headers") - - var headersToSync []scale.HeaderUpdatePayload - - // start syncing at next block after last synced block - currentSlot := fromSlot - headerUpdate, err := h.getNextHeaderUpdateBySlot(currentSlot) - if err != nil { - return fmt.Errorf("get next header update by slot with ancestry proof: %w", err) - } - currentSlot = uint64(headerUpdate.Header.Slot) - - for currentSlot <= toSlot { - log.WithFields(log.Fields{ - "currentSlot": currentSlot, - }).Info("fetching next header at slot") - - var nextHeaderUpdate scale.HeaderUpdatePayload - if currentSlot >= toSlot { - // Just construct an empty update so to break the loop - nextHeaderUpdate = scale.HeaderUpdatePayload{Header: scale.BeaconHeader{Slot: types.U64(toSlot + 1)}} - } else { - // To get the sync witness for the current synced header. This header - // will be used as the next update. - nextHeaderUpdate, err = h.getNextHeaderUpdateBySlot(currentSlot) - if err != nil { - return fmt.Errorf("get next header update by slot with ancestry proof: %w", err) - } - } - - headersToSync = append(headersToSync, headerUpdate) - // last slot to be synced, sync headers - if currentSlot >= toSlot { - err = h.batchSyncHeaders(ctx, headersToSync) - if err != nil { - return fmt.Errorf("batch sync headers failed: %w", err) - } - } - headerUpdate = nextHeaderUpdate - currentSlot = uint64(headerUpdate.Header.Slot) - } - // waiting for all batch calls to be executed on chain - err = h.waitingForBatchCallFinished(toSlot) - if err != nil { - return err - } - h.cache.SetLastSyncedExecutionSlot(toSlot) - return nil -} - func (h *Header) syncLaggingSyncCommitteePeriods(ctx context.Context, latestSyncedPeriod, currentSyncPeriod uint64) error { - // sync for the next period - periodsToSync := []uint64{latestSyncedPeriod + 1} + // sync for all missing periods + periodsToSync := []uint64{} + for i := latestSyncedPeriod + 1; i <= currentSyncPeriod; i++ { + periodsToSync = append(periodsToSync, i) + } // For initialPeriod special handling here to sync it again for nextSyncCommittee which is not included in InitCheckpoint if h.isInitialSyncPeriod() { @@ -353,6 +282,13 @@ func (h *Header) populateClosestCheckpoint(slot uint64) (cache.Proof, error) { checkpointSlot := checkpoint.Slot if checkpointSlot == 0 { checkpointSlot = h.syncer.CalculateNextCheckpointSlot(slot) + lastFinalizedHeaderState, err := h.writer.GetLastFinalizedHeaderState() + if err != nil { + return cache.Proof{}, fmt.Errorf("fetch parachain last finalized header state: %w", err) + } + if checkpointSlot > lastFinalizedHeaderState.BeaconSlot { + checkpointSlot = lastFinalizedHeaderState.BeaconSlot + } log.WithFields(log.Fields{"calculatedCheckpointSlot": checkpointSlot}).Info("checkpoint slot not available, try with slot in next sync period instead") } err := h.populateFinalizedCheckpoint(checkpointSlot) @@ -377,8 +313,7 @@ func (h *Header) populateClosestCheckpoint(slot uint64) (cache.Proof, error) { return checkpoint, nil } -func (h *Header) getNextHeaderUpdateBySlot(slot uint64) (scale.HeaderUpdatePayload, error) { - slot = slot + 1 +func (h *Header) getHeaderUpdateBySlot(slot uint64) (scale.HeaderUpdatePayload, error) { header, err := h.syncer.FindBeaconHeaderWithBlockIncluded(slot) if err != nil { return scale.HeaderUpdatePayload{}, fmt.Errorf("get next beacon header with block included: %w", err) @@ -394,14 +329,33 @@ func (h *Header) getNextHeaderUpdateBySlot(slot uint64) (scale.HeaderUpdatePaylo return h.syncer.GetHeaderUpdate(blockRoot, &checkpoint) } -func (h *Header) batchSyncHeaders(ctx context.Context, headerUpdates []scale.HeaderUpdatePayload) error { - headerUpdatesInf := make([]interface{}, len(headerUpdates)) - for i, v := range headerUpdates { - headerUpdatesInf[i] = v +func (h *Header) SyncExecutionHeader(ctx context.Context, blockRoot common.Hash) error { + header, err := h.syncer.Client.GetHeader(blockRoot) + if err != nil { + return fmt.Errorf("get beacon header by blockRoot: %w", err) } - err := h.writer.BatchCall(ctx, "EthereumBeaconClient.submit_execution_header", headerUpdatesInf) + headerUpdate, err := h.getHeaderUpdateBySlot(header.Slot) if err != nil { - return err + return fmt.Errorf("get header update by slot with ancestry proof: %w", err) + } + err = h.writer.WriteToParachainAndWatch(ctx, "EthereumBeaconClient.submit_execution_header", headerUpdate) + if err != nil { + return fmt.Errorf("submit_execution_header: %w", err) + } + var blockHash types.H256 + if headerUpdate.ExecutionHeader.Deneb != nil { + blockHash = headerUpdate.ExecutionHeader.Deneb.BlockHash + } else if headerUpdate.ExecutionHeader.Capella != nil { + blockHash = headerUpdate.ExecutionHeader.Capella.BlockHash + } else { + return fmt.Errorf("invalid blockHash in headerUpdate") + } + compactExecutionHeaderState, err := h.writer.GetCompactExecutionHeaderStateByBlockHash(blockHash) + if err != nil { + return fmt.Errorf("get compactExecutionHeaderState by blockHash: %w", err) + } + if compactExecutionHeaderState.BlockNumber == 0 { + return fmt.Errorf("invalid compactExecutionHeaderState") } return nil } @@ -411,24 +365,3 @@ func (h *Header) isInitialSyncPeriod() bool { lastFinalizedPeriod := h.syncer.ComputeSyncPeriodAtSlot(h.cache.Finalized.LastSyncedSlot) return initialPeriod == lastFinalizedPeriod } - -func (h *Header) waitingForBatchCallFinished(toSlot uint64) error { - batchCallFinished := false - cnt := 0 - for cnt <= 12 { - executionHeaderState, err := h.writer.GetLastExecutionHeaderState() - if err != nil { - return fmt.Errorf("fetch last execution hash: %w", err) - } - if executionHeaderState.BeaconSlot == toSlot { - batchCallFinished = true - break - } - time.Sleep(6 * time.Second) - cnt++ - } - if !batchCallFinished { - return ErrExecutionHeaderNotImported - } - return nil -} diff --git a/relayer/relays/beacon/header/syncer/api/api.go b/relayer/relays/beacon/header/syncer/api/api.go index 1c87de7ec..8cec45e31 100644 --- a/relayer/relays/beacon/header/syncer/api/api.go +++ b/relayer/relays/beacon/header/syncer/api/api.go @@ -14,17 +14,14 @@ import ( "github.com/snowfork/snowbridge/relayer/relays/util" ) -const ( - ConstructRequestErrorMessage = "construct header request" - DoHTTPRequestErrorMessage = "do http request" - HTTPStatusNotOKErrorMessage = "http status not ok" - ReadResponseBodyErrorMessage = "read response body" - UnmarshalBodyErrorMessage = "unmarshal body" -) - var ( ErrNotFound = errors.New("not found") ErrSyncCommitteeUpdateNotAvailable = errors.New("no sync committee update available") + ConstructRequestErrorMessage = "construct header request" + DoHTTPRequestErrorMessage = "do http request" + HTTPStatusNotOKErrorMessage = "http status not ok" + ReadResponseBodyErrorMessage = "read response body" + UnmarshalBodyErrorMessage = "unmarshal body" ) type BeaconClient struct { @@ -42,30 +39,30 @@ func NewBeaconClient(endpoint string, slotsInEpoch uint64) *BeaconClient { } func (b *BeaconClient) GetBootstrap(blockRoot common.Hash) (BootstrapResponse, error) { + var response BootstrapResponse req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/eth/v1/beacon/light_client/bootstrap/%s", b.endpoint, blockRoot), nil) if err != nil { - return BootstrapResponse{}, fmt.Errorf("%s: %w", ConstructRequestErrorMessage, err) + return response, fmt.Errorf("%s: %w", ConstructRequestErrorMessage, err) } req.Header.Set("accept", "application/json") res, err := b.httpClient.Do(req) if err != nil { - return BootstrapResponse{}, fmt.Errorf("%s: %w", DoHTTPRequestErrorMessage, err) + return response, fmt.Errorf("%s: %w", DoHTTPRequestErrorMessage, err) } if res.StatusCode != http.StatusOK { - return BootstrapResponse{}, fmt.Errorf("%s: %d", HTTPStatusNotOKErrorMessage, res.StatusCode) + return response, fmt.Errorf("%s: %d", HTTPStatusNotOKErrorMessage, res.StatusCode) } bodyBytes, err := io.ReadAll(res.Body) if err != nil { - return BootstrapResponse{}, fmt.Errorf("%s: %w", ReadResponseBodyErrorMessage, err) + return response, fmt.Errorf("%s: %w", ReadResponseBodyErrorMessage, err) } - var response BootstrapResponse err = json.Unmarshal(bodyBytes, &response) if err != nil { - return BootstrapResponse{}, fmt.Errorf("%s: %w", UnmarshalBodyErrorMessage, err) + return response, fmt.Errorf("%s: %w", UnmarshalBodyErrorMessage, err) } return response, nil diff --git a/relayer/relays/beacon/header/syncer/api/api_deneb.go b/relayer/relays/beacon/header/syncer/api/api_deneb.go index f95a71232..3ebe17f98 100644 --- a/relayer/relays/beacon/header/syncer/api/api_deneb.go +++ b/relayer/relays/beacon/header/syncer/api/api_deneb.go @@ -1,11 +1,14 @@ package api import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" "github.com/snowfork/go-substrate-rpc-client/v4/types" + beaconjson "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/json" "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/scale" "github.com/snowfork/snowbridge/relayer/relays/beacon/state" "github.com/snowfork/snowbridge/relayer/relays/util" - "math/big" ) func DenebExecutionPayloadToScale(e *state.ExecutionPayloadDeneb) (scale.ExecutionPayloadHeaderDeneb, error) { @@ -51,3 +54,50 @@ func DenebExecutionPayloadToScale(e *state.ExecutionPayloadDeneb) (scale.Executi ExcessBlobGas: types.NewU64(e.ExcessBlobGas), }, nil } + +func DenebJsonExecutionPayloadHeaderToScale(e *beaconjson.FullExecutionPayloadHeaderJson) (scale.ExecutionPayloadHeaderDeneb, error) { + var executionPayloadHeader scale.ExecutionPayloadHeaderDeneb + var baseFeePerGas big.Int + baseFeePerGasU64, err := util.ToUint64(e.BaseFeePerGas) + if err != nil { + return executionPayloadHeader, err + } + blockNumber, err := util.ToUint64(e.BlockNumber) + if err != nil { + return executionPayloadHeader, err + } + baseFeePerGas.SetUint64(baseFeePerGasU64) + gasLimit, err := util.ToUint64(e.GasLimit) + if err != nil { + return executionPayloadHeader, err + } + gasUsed, err := util.ToUint64(e.GasUsed) + if err != nil { + return executionPayloadHeader, err + } + timestamp, err := util.ToUint64(e.Timestamp) + if err != nil { + return executionPayloadHeader, err + } + blobGasUsed, _ := util.ToUint64(e.BlobGasUsed) + excessBlobGas, _ := util.ToUint64(e.ExcessBlobGas) + return scale.ExecutionPayloadHeaderDeneb{ + ParentHash: types.NewH256(common.HexToHash(e.ParentHash).Bytes()), + FeeRecipient: types.NewH160(common.HexToAddress(e.FeeRecipient).Bytes()), + StateRoot: types.NewH256(common.HexToHash(e.StateRoot).Bytes()), + ReceiptsRoot: types.NewH256(common.HexToHash(e.ReceiptsRoot).Bytes()), + LogsBloom: common.FromHex(e.LogsBloom), + PrevRandao: types.NewH256(common.HexToHash(e.PrevRandao).Bytes()), + BlockNumber: types.NewU64(blockNumber), + GasLimit: types.NewU64(gasLimit), + GasUsed: types.NewU64(gasUsed), + Timestamp: types.NewU64(timestamp), + ExtraData: common.FromHex(e.ExtraData), + BaseFeePerGas: types.NewU256(baseFeePerGas), + BlockHash: types.NewH256(common.HexToHash(e.BlockHash).Bytes()), + TransactionsRoot: types.NewH256(common.HexToHash(e.TransactionsRoot).Bytes()), + WithdrawalsRoot: types.NewH256(common.HexToHash(e.WithdrawalsRoot).Bytes()), + BlobGasUsed: types.NewU64(blobGasUsed), + ExcessBlobGas: types.NewU64(excessBlobGas), + }, nil +} diff --git a/relayer/relays/beacon/header/syncer/api/api_response.go b/relayer/relays/beacon/header/syncer/api/api_response.go index ff74a69ba..2939432d2 100644 --- a/relayer/relays/beacon/header/syncer/api/api_response.go +++ b/relayer/relays/beacon/header/syncer/api/api_response.go @@ -15,17 +15,13 @@ import ( type SyncCommitteePeriodUpdateResponse struct { Data struct { - AttestedHeader struct { - Beacon HeaderResponse `json:"beacon"` - } `json:"attested_header"` - NextSyncCommittee SyncCommitteeResponse `json:"next_sync_committee"` - NextSyncCommitteeBranch []string `json:"next_sync_committee_branch"` - FinalizedHeader struct { - Beacon HeaderResponse `json:"beacon"` - } `json:"finalized_header"` - FinalityBranch []string `json:"finality_branch"` - SyncAggregate SyncAggregateResponse `json:"sync_aggregate"` - SignatureSlot string `json:"signature_slot"` + AttestedHeader BeaconHeaderWithExecutionHeaderResponse `json:"attested_header"` + NextSyncCommittee SyncCommitteeResponse `json:"next_sync_committee"` + NextSyncCommitteeBranch []string `json:"next_sync_committee_branch"` + FinalizedHeader BeaconHeaderWithExecutionHeaderResponse `json:"finalized_header"` + FinalityBranch []string `json:"finality_branch"` + SyncAggregate SyncAggregateResponse `json:"sync_aggregate"` + SignatureSlot string `json:"signature_slot"` } `json:"data"` } @@ -78,11 +74,9 @@ type BeaconBlockResponse struct { type BootstrapResponse struct { Data struct { - Header struct { - Beacon HeaderResponse `json:"beacon"` - } `json:"header"` - CurrentSyncCommittee SyncCommitteeResponse `json:"current_sync_committee"` - CurrentSyncCommitteeBranch []string `json:"current_sync_committee_branch"` + Header BeaconHeaderWithExecutionHeaderResponse `json:"header"` + CurrentSyncCommittee SyncCommitteeResponse `json:"current_sync_committee"` + CurrentSyncCommitteeBranch []string `json:"current_sync_committee_branch"` } `json:"data"` } @@ -259,17 +253,19 @@ type ForkResponse struct { } `json:"data"` } +type BeaconHeaderWithExecutionHeaderResponse struct { + Beacon HeaderResponse `json:"beacon"` + Execution beaconjson.FullExecutionPayloadHeaderJson `json:"execution,omitempty"` + ExecutionBranch []string `json:"execution_branch,omitempty"` +} + type LatestFinalisedUpdateResponse struct { Data struct { - AttestedHeader struct { - Beacon HeaderResponse `json:"beacon"` - } `json:"attested_header"` - FinalizedHeader struct { - Beacon HeaderResponse `json:"beacon"` - } `json:"finalized_header"` - FinalityBranch []string `json:"finality_branch"` - SyncAggregate SyncAggregateResponse `json:"sync_aggregate"` - SignatureSlot string `json:"signature_slot"` + AttestedHeader BeaconHeaderWithExecutionHeaderResponse `json:"attested_header"` + FinalizedHeader BeaconHeaderWithExecutionHeaderResponse `json:"finalized_header"` + FinalityBranch []string `json:"finality_branch"` + SyncAggregate SyncAggregateResponse `json:"sync_aggregate"` + SignatureSlot string `json:"signature_slot"` } `json:"data"` } @@ -1039,3 +1035,46 @@ func CapellaExecutionPayloadToScale(e *state.ExecutionPayloadCapella) (scale.Exe WithdrawalsRoot: withdrawalRoot, }, nil } + +func CapellaJsonExecutionPayloadHeaderToScale(e *beaconjson.FullExecutionPayloadHeaderJson) (scale.ExecutionPayloadHeaderCapella, error) { + var executionPayloadHeader scale.ExecutionPayloadHeaderCapella + var baseFeePerGas big.Int + baseFeePerGasU64, err := util.ToUint64(e.BaseFeePerGas) + if err != nil { + return executionPayloadHeader, err + } + blockNumber, err := util.ToUint64(e.BlockNumber) + if err != nil { + return executionPayloadHeader, err + } + baseFeePerGas.SetUint64(baseFeePerGasU64) + gasLimit, err := util.ToUint64(e.GasLimit) + if err != nil { + return executionPayloadHeader, err + } + gasUsed, err := util.ToUint64(e.GasUsed) + if err != nil { + return executionPayloadHeader, err + } + timestamp, err := util.ToUint64(e.Timestamp) + if err != nil { + return executionPayloadHeader, err + } + return scale.ExecutionPayloadHeaderCapella{ + ParentHash: types.NewH256(common.HexToHash(e.ParentHash).Bytes()), + FeeRecipient: types.NewH160(common.HexToAddress(e.FeeRecipient).Bytes()), + StateRoot: types.NewH256(common.HexToHash(e.StateRoot).Bytes()), + ReceiptsRoot: types.NewH256(common.HexToHash(e.ReceiptsRoot).Bytes()), + LogsBloom: common.Hex2Bytes(e.LogsBloom), + PrevRandao: types.NewH256(common.HexToHash(e.PrevRandao).Bytes()), + BlockNumber: types.NewU64(blockNumber), + GasLimit: types.NewU64(gasLimit), + GasUsed: types.NewU64(gasUsed), + Timestamp: types.NewU64(timestamp), + ExtraData: common.Hex2Bytes(e.ExtraData), + BaseFeePerGas: types.NewU256(baseFeePerGas), + BlockHash: types.NewH256(common.HexToHash(e.ParentHash).Bytes()), + TransactionsRoot: types.NewH256(common.HexToHash(e.TransactionsRoot).Bytes()), + WithdrawalsRoot: types.NewH256(common.HexToHash(e.WithdrawalsRoot).Bytes()), + }, nil +} diff --git a/relayer/relays/beacon/header/syncer/json/beacon_json.go b/relayer/relays/beacon/header/syncer/json/beacon_json.go index e53f6e467..cda9afae1 100644 --- a/relayer/relays/beacon/header/syncer/json/beacon_json.go +++ b/relayer/relays/beacon/header/syncer/json/beacon_json.go @@ -32,14 +32,16 @@ type SyncAggregate struct { } type Update struct { - AttestedHeader BeaconHeader `json:"attested_header"` - SyncAggregate SyncAggregate `json:"sync_aggregate"` - SignatureSlot uint64 `json:"signature_slot"` - NextSyncCommitteeUpdate *NextSyncCommitteeUpdate `json:"next_sync_committee_update"` - FinalizedHeader BeaconHeader `json:"finalized_header"` - FinalityBranch []string `json:"finality_branch"` - BlockRootsRoot string `json:"block_roots_root"` - BlockRootsBranch []string `json:"block_roots_branch"` + AttestedHeader BeaconHeader `json:"attested_header"` + SyncAggregate SyncAggregate `json:"sync_aggregate"` + SignatureSlot uint64 `json:"signature_slot"` + NextSyncCommitteeUpdate *NextSyncCommitteeUpdate `json:"next_sync_committee_update"` + FinalizedHeader BeaconHeader `json:"finalized_header"` + FinalityBranch []string `json:"finality_branch"` + BlockRootsRoot string `json:"block_roots_root"` + BlockRootsBranch []string `json:"block_roots_branch"` + ExecutionHeader *VersionedExecutionPayloadHeader `json:"execution_header"` + ExecutionBranch *[]string `json:"execution_branch"` } type NextSyncCommitteeUpdate struct { @@ -81,30 +83,22 @@ type SignedHeader struct { Signature string `json:"signature"` } -type Block struct { - Slot uint64 `json:"slot"` - ProposerIndex uint64 `json:"proposer_index"` - ParentRoot string `json:"parent_root"` - StateRoot string `json:"state_root"` - Body BlockBody `json:"body"` -} - type ExecutionPayloadHeaderCapella struct { - ParentHash string `json:"parent_hash"` - FeeRecipient string `json:"fee_recipient"` - StateRoot string `json:"state_root"` - ReceiptsRoot string `json:"receipts_root"` - LogsBloom string `json:"logs_bloom"` - PrevRandao string `json:"prev_randao"` - BlockNumber uint64 `json:"block_number"` - GasLimit uint64 `json:"gas_limit"` - GasUsed uint64 `json:"gas_used"` - Timestamp uint64 `json:"timestamp"` - ExtraData string `json:"extra_data"` - BaseFeePerGas uint64 `json:"base_fee_per_gas"` - BlockHash string `json:"block_hash"` - TransactionRoot string `json:"transactions_root"` - WithdrawalsRoot string `json:"withdrawals_root"` + ParentHash string `json:"parent_hash"` + FeeRecipient string `json:"fee_recipient"` + StateRoot string `json:"state_root"` + ReceiptsRoot string `json:"receipts_root"` + LogsBloom string `json:"logs_bloom"` + PrevRandao string `json:"prev_randao"` + BlockNumber uint64 `json:"block_number"` + GasLimit uint64 `json:"gas_limit"` + GasUsed uint64 `json:"gas_used"` + Timestamp uint64 `json:"timestamp"` + ExtraData string `json:"extra_data"` + BaseFeePerGas uint64 `json:"base_fee_per_gas"` + BlockHash string `json:"block_hash"` + TransactionsRoot string `json:"transactions_root"` + WithdrawalsRoot string `json:"withdrawals_root"` } type CompactExecutionHeader struct { @@ -235,34 +229,6 @@ func (d *Deposit) RemoveLeadingZeroHashes() { d.Data.WithdrawalCredentials = removeLeadingZeroHash(d.Data.WithdrawalCredentials) } -func (b *Block) RemoveLeadingZeroHashes() { - b.ParentRoot = removeLeadingZeroHash(b.ParentRoot) - b.StateRoot = removeLeadingZeroHash(b.StateRoot) - b.Body.RandaoReveal = removeLeadingZeroHash(b.Body.RandaoReveal) - b.Body.Eth1Data.DepositRoot = removeLeadingZeroHash(b.Body.Eth1Data.DepositRoot) - b.Body.Eth1Data.BlockHash = removeLeadingZeroHash(b.Body.Eth1Data.BlockHash) - b.Body.Graffiti = removeLeadingZeroHash(b.Body.Graffiti) - - for i := range b.Body.ProposerSlashings { - b.Body.ProposerSlashings[i].RemoveLeadingZeroHashes() - } - - for i := range b.Body.AttesterSlashings { - b.Body.AttesterSlashings[i].RemoveLeadingZeroHashes() - } - - for i := range b.Body.Attestations { - b.Body.Attestations[i].RemoveLeadingZeroHashes() - } - - for i := range b.Body.Deposits { - b.Body.Deposits[i].RemoveLeadingZeroHashes() - } - - b.Body.SyncAggregate.RemoveLeadingZeroHashes() - b.Body.ExecutionPayload.RemoveLeadingZeroHashes() -} - func (e *ExecutionPayloadHeaderCapella) RemoveLeadingZeroHashes() { e.ParentHash = removeLeadingZeroHash(e.ParentHash) e.FeeRecipient = removeLeadingZeroHash(e.FeeRecipient) @@ -272,7 +238,7 @@ func (e *ExecutionPayloadHeaderCapella) RemoveLeadingZeroHashes() { e.PrevRandao = removeLeadingZeroHash(e.PrevRandao) e.ExtraData = removeLeadingZeroHash(e.ExtraData) e.BlockHash = removeLeadingZeroHash(e.BlockHash) - e.TransactionRoot = removeLeadingZeroHash(e.TransactionRoot) + e.TransactionsRoot = removeLeadingZeroHash(e.TransactionsRoot) e.WithdrawalsRoot = removeLeadingZeroHash(e.WithdrawalsRoot) } diff --git a/relayer/relays/beacon/header/syncer/json/beacon_json_deneb.go b/relayer/relays/beacon/header/syncer/json/beacon_json_deneb.go index 7df531f11..1c05aed0e 100644 --- a/relayer/relays/beacon/header/syncer/json/beacon_json_deneb.go +++ b/relayer/relays/beacon/header/syncer/json/beacon_json_deneb.go @@ -1,23 +1,43 @@ package json type ExecutionPayloadHeaderDeneb struct { - ParentHash string `json:"parent_hash"` - FeeRecipient string `json:"fee_recipient"` - StateRoot string `json:"state_root"` - ReceiptsRoot string `json:"receipts_root"` - LogsBloom string `json:"logs_bloom"` - PrevRandao string `json:"prev_randao"` - BlockNumber uint64 `json:"block_number"` - GasLimit uint64 `json:"gas_limit"` - GasUsed uint64 `json:"gas_used"` - Timestamp uint64 `json:"timestamp"` - ExtraData string `json:"extra_data"` - BaseFeePerGas uint64 `json:"base_fee_per_gas"` - BlockHash string `json:"block_hash"` - TransactionRoot string `json:"transactions_root"` - WithdrawalsRoot string `json:"withdrawals_root"` - BlobGasUsed uint64 `json:"blob_gas_used"` - ExcessBlobGas uint64 `json:"excess_blob_gas"` + ParentHash string `json:"parent_hash"` + FeeRecipient string `json:"fee_recipient"` + StateRoot string `json:"state_root"` + ReceiptsRoot string `json:"receipts_root"` + LogsBloom string `json:"logs_bloom"` + PrevRandao string `json:"prev_randao"` + BlockNumber uint64 `json:"block_number"` + GasLimit uint64 `json:"gas_limit"` + GasUsed uint64 `json:"gas_used"` + Timestamp uint64 `json:"timestamp"` + ExtraData string `json:"extra_data"` + BaseFeePerGas uint64 `json:"base_fee_per_gas"` + BlockHash string `json:"block_hash"` + TransactionsRoot string `json:"transactions_root"` + WithdrawalsRoot string `json:"withdrawals_root"` + BlobGasUsed uint64 `json:"blob_gas_used"` + ExcessBlobGas uint64 `json:"excess_blob_gas"` +} + +type FullExecutionPayloadHeaderJson struct { + ParentHash string `json:"parent_hash"` + FeeRecipient string `json:"fee_recipient"` + StateRoot string `json:"state_root"` + ReceiptsRoot string `json:"receipts_root"` + LogsBloom string `json:"logs_bloom"` + PrevRandao string `json:"prev_randao"` + BlockNumber string `json:"block_number"` + GasLimit string `json:"gas_limit"` + GasUsed string `json:"gas_used"` + Timestamp string `json:"timestamp"` + ExtraData string `json:"extra_data"` + BaseFeePerGas string `json:"base_fee_per_gas"` + BlockHash string `json:"block_hash"` + TransactionsRoot string `json:"transactions_root"` + WithdrawalsRoot string `json:"withdrawals_root"` + BlobGasUsed string `json:"blob_gas_used,omitempty"` + ExcessBlobGas string `json:"excess_blob_gas,omitempty"` } func (e *ExecutionPayloadHeaderDeneb) RemoveLeadingZeroHashes() { @@ -29,6 +49,6 @@ func (e *ExecutionPayloadHeaderDeneb) RemoveLeadingZeroHashes() { e.PrevRandao = removeLeadingZeroHash(e.PrevRandao) e.ExtraData = removeLeadingZeroHash(e.ExtraData) e.BlockHash = removeLeadingZeroHash(e.BlockHash) - e.TransactionRoot = removeLeadingZeroHash(e.TransactionRoot) + e.TransactionsRoot = removeLeadingZeroHash(e.TransactionsRoot) e.WithdrawalsRoot = removeLeadingZeroHash(e.WithdrawalsRoot) } diff --git a/relayer/relays/beacon/header/syncer/scale/beacon_deneb.go b/relayer/relays/beacon/header/syncer/scale/beacon_deneb.go index 6eb428af4..20cc9b719 100644 --- a/relayer/relays/beacon/header/syncer/scale/beacon_deneb.go +++ b/relayer/relays/beacon/header/syncer/scale/beacon_deneb.go @@ -28,22 +28,22 @@ type ExecutionPayloadHeaderDeneb struct { func (e *ExecutionPayloadHeaderDeneb) ToJSON() json.ExecutionPayloadHeaderDeneb { return json.ExecutionPayloadHeaderDeneb{ - ParentHash: e.ParentHash.Hex(), - FeeRecipient: util.BytesToHexString(e.FeeRecipient[:]), - StateRoot: e.StateRoot.Hex(), - ReceiptsRoot: e.ReceiptsRoot.Hex(), - LogsBloom: util.BytesToHexString(e.LogsBloom), - PrevRandao: e.PrevRandao.Hex(), - BlockNumber: uint64(e.BlockNumber), - GasLimit: uint64(e.GasLimit), - GasUsed: uint64(e.GasUsed), - Timestamp: uint64(e.Timestamp), - ExtraData: util.BytesToHexString(e.ExtraData), - BaseFeePerGas: e.BaseFeePerGas.Uint64(), - BlockHash: e.BlockHash.Hex(), - TransactionRoot: e.TransactionsRoot.Hex(), - WithdrawalsRoot: e.WithdrawalsRoot.Hex(), - BlobGasUsed: uint64(e.BlobGasUsed), - ExcessBlobGas: uint64(e.ExcessBlobGas), + ParentHash: e.ParentHash.Hex(), + FeeRecipient: util.BytesToHexString(e.FeeRecipient[:]), + StateRoot: e.StateRoot.Hex(), + ReceiptsRoot: e.ReceiptsRoot.Hex(), + LogsBloom: util.BytesToHexString(e.LogsBloom), + PrevRandao: e.PrevRandao.Hex(), + BlockNumber: uint64(e.BlockNumber), + GasLimit: uint64(e.GasLimit), + GasUsed: uint64(e.GasUsed), + Timestamp: uint64(e.Timestamp), + ExtraData: util.BytesToHexString(e.ExtraData), + BaseFeePerGas: e.BaseFeePerGas.Uint64(), + BlockHash: e.BlockHash.Hex(), + TransactionsRoot: e.TransactionsRoot.Hex(), + WithdrawalsRoot: e.WithdrawalsRoot.Hex(), + BlobGasUsed: uint64(e.BlobGasUsed), + ExcessBlobGas: uint64(e.ExcessBlobGas), } } diff --git a/relayer/relays/beacon/header/syncer/scale/beacon_scale.go b/relayer/relays/beacon/header/syncer/scale/beacon_scale.go index e37332210..fc6bd4a58 100644 --- a/relayer/relays/beacon/header/syncer/scale/beacon_scale.go +++ b/relayer/relays/beacon/header/syncer/scale/beacon_scale.go @@ -41,6 +41,26 @@ type UpdatePayload struct { FinalityBranch []types.H256 BlockRootsRoot types.H256 BlockRootsBranch []types.H256 + ExecutionHeader OptionExecutionHeader + ExecutionBranch OptionExecutionBranch +} + +type OptionExecutionHeader struct { + HasValue bool + Value VersionedExecutionPayloadHeader +} + +func (o OptionExecutionHeader) Encode(encoder scale.Encoder) error { + return encoder.EncodeOption(o.HasValue, o.Value) +} + +type OptionExecutionBranch struct { + HasValue bool + Value []types.H256 +} + +func (o OptionExecutionBranch) Encode(encoder scale.Encoder) error { + return encoder.EncodeOption(o.HasValue, o.Value) } type OptionNextSyncCommitteeUpdatePayload struct { @@ -318,3 +338,10 @@ func (v VersionedExecutionPayloadHeader) Encode(encoder scale.Encoder) error { } return err } + +type CompactExecutionHeader struct { + ParentHash types.H256 + BlockNumber types.UCompact + StateRoot types.H256 + ReceiptsRoot types.H256 +} diff --git a/relayer/relays/beacon/header/syncer/scale/json_conversion.go b/relayer/relays/beacon/header/syncer/scale/json_conversion.go index 9e59e1067..24800f0f8 100644 --- a/relayer/relays/beacon/header/syncer/scale/json_conversion.go +++ b/relayer/relays/beacon/header/syncer/scale/json_conversion.go @@ -24,6 +24,15 @@ func (p UpdatePayload) ToJSON() json.Update { NextSyncCommitteeBranch: util.ScaleBranchToString(p.NextSyncCommitteeUpdate.Value.NextSyncCommitteeBranch), } } + var executionHeader json.VersionedExecutionPayloadHeader + if p.ExecutionHeader.HasValue { + executionHeader = p.ExecutionHeader.Value.ToJSON() + } + + var executionBranch []string + if p.ExecutionBranch.HasValue { + executionBranch = util.ScaleBranchToString(p.ExecutionBranch.Value) + } return json.Update{ AttestedHeader: p.AttestedHeader.ToJSON(), @@ -34,6 +43,8 @@ func (p UpdatePayload) ToJSON() json.Update { FinalityBranch: util.ScaleBranchToString(p.FinalityBranch), BlockRootsRoot: p.BlockRootsRoot.Hex(), BlockRootsBranch: util.ScaleBranchToString(p.BlockRootsBranch), + ExecutionHeader: &executionHeader, + ExecutionBranch: &executionBranch, } } @@ -65,21 +76,21 @@ func (b *BeaconHeader) ToJSON() json.BeaconHeader { func (e *ExecutionPayloadHeaderCapella) ToJSON() json.ExecutionPayloadHeaderCapella { return json.ExecutionPayloadHeaderCapella{ - ParentHash: e.ParentHash.Hex(), - FeeRecipient: util.BytesToHexString(e.FeeRecipient[:]), - StateRoot: e.StateRoot.Hex(), - ReceiptsRoot: e.ReceiptsRoot.Hex(), - LogsBloom: util.BytesToHexString(e.LogsBloom), - PrevRandao: e.PrevRandao.Hex(), - BlockNumber: uint64(e.BlockNumber), - GasLimit: uint64(e.GasLimit), - GasUsed: uint64(e.GasUsed), - Timestamp: uint64(e.Timestamp), - ExtraData: util.BytesToHexString(e.ExtraData), - BaseFeePerGas: e.BaseFeePerGas.Uint64(), - BlockHash: e.BlockHash.Hex(), - TransactionRoot: e.TransactionsRoot.Hex(), - WithdrawalsRoot: e.WithdrawalsRoot.Hex(), + ParentHash: e.ParentHash.Hex(), + FeeRecipient: util.BytesToHexString(e.FeeRecipient[:]), + StateRoot: e.StateRoot.Hex(), + ReceiptsRoot: e.ReceiptsRoot.Hex(), + LogsBloom: util.BytesToHexString(e.LogsBloom), + PrevRandao: e.PrevRandao.Hex(), + BlockNumber: uint64(e.BlockNumber), + GasLimit: uint64(e.GasLimit), + GasUsed: uint64(e.GasUsed), + Timestamp: uint64(e.Timestamp), + ExtraData: util.BytesToHexString(e.ExtraData), + BaseFeePerGas: e.BaseFeePerGas.Uint64(), + BlockHash: e.BlockHash.Hex(), + TransactionsRoot: e.TransactionsRoot.Hex(), + WithdrawalsRoot: e.WithdrawalsRoot.Hex(), } } diff --git a/relayer/relays/beacon/header/syncer/syncer.go b/relayer/relays/beacon/header/syncer/syncer.go index af91246be..93c017762 100644 --- a/relayer/relays/beacon/header/syncer/syncer.go +++ b/relayer/relays/beacon/header/syncer/syncer.go @@ -71,6 +71,10 @@ func (s *Syncer) GetCheckpoint() (scale.BeaconCheckpoint, error) { return scale.BeaconCheckpoint{}, fmt.Errorf("convert sync committee to scale: %w", err) } + if err != nil { + return scale.BeaconCheckpoint{}, fmt.Errorf("get sync committee: %w", err) + } + return scale.BeaconCheckpoint{ Header: header, CurrentSyncCommittee: syncCommittee, @@ -124,6 +128,11 @@ func (s *Syncer) GetSyncCommitteePeriodUpdate(from uint64) (scale.Update, error) return scale.Update{}, fmt.Errorf("beacon header hash tree root: %w", err) } + executionPayloadHeader, executionBranch, err := s.getExecutionHeaderFromBeaconHeader(committeeUpdateContainer.Data.FinalizedHeader) + if err != nil { + return scale.Update{}, fmt.Errorf("get execution header from beacon header: %w", err) + } + syncCommitteePeriodUpdate := scale.Update{ Payload: scale.UpdatePayload{ AttestedHeader: attestedHeader, @@ -140,6 +149,14 @@ func (s *Syncer) GetSyncCommitteePeriodUpdate(from uint64) (scale.Update, error) FinalityBranch: util.ProofBranchToScale(committeeUpdate.FinalityBranch), BlockRootsRoot: blockRootsProof.Leaf, BlockRootsBranch: blockRootsProof.Proof, + ExecutionHeader: scale.OptionExecutionHeader{ + HasValue: true, + Value: executionPayloadHeader, + }, + ExecutionBranch: scale.OptionExecutionBranch{ + HasValue: true, + Value: executionBranch, + }, }, FinalizedHeaderBlockRoot: finalizedHeaderBlockRoot, BlockRootsTree: blockRootsProof.Tree, @@ -244,6 +261,11 @@ func (s *Syncer) GetFinalizedUpdate() (scale.Update, error) { return scale.Update{}, fmt.Errorf("parse signature slot as int: %w", err) } + executionPayloadHeader, executionBranch, err := s.getExecutionHeaderFromBeaconHeader(finalizedUpdate.Data.FinalizedHeader) + if err != nil { + return scale.Update{}, fmt.Errorf("get execution header from beacon header: %w", err) + } + updatePayload := scale.UpdatePayload{ AttestedHeader: attestedHeader, SyncAggregate: syncAggregate, @@ -255,6 +277,14 @@ func (s *Syncer) GetFinalizedUpdate() (scale.Update, error) { FinalityBranch: util.ProofBranchToScale(finalizedUpdate.Data.FinalityBranch), BlockRootsRoot: blockRootsProof.Leaf, BlockRootsBranch: blockRootsProof.Proof, + ExecutionHeader: scale.OptionExecutionHeader{ + HasValue: true, + Value: executionPayloadHeader, + }, + ExecutionBranch: scale.OptionExecutionBranch{ + HasValue: true, + Value: executionBranch, + }, } return scale.Update{ @@ -352,7 +382,7 @@ func (s *Syncer) FindBeaconHeaderWithBlockIncluded(slot uint64) (state.BeaconBlo return beaconHeader, nil } -func (s *Syncer) GetNextHeaderUpdateBySlotWithCheckpoint(slot uint64, checkpoint *cache.Proof) (scale.HeaderUpdatePayload, error) { +func (s *Syncer) GetHeaderUpdateBySlotWithCheckpoint(slot uint64, checkpoint *cache.Proof) (scale.HeaderUpdatePayload, error) { header, err := s.FindBeaconHeaderWithBlockIncluded(slot) if err != nil { return scale.HeaderUpdatePayload{}, fmt.Errorf("get next beacon header with block included: %w", err) @@ -480,3 +510,30 @@ func (s *Syncer) getExecutionHeaderBranch(block state.BeaconBlock) ([]types.H256 return util.BytesBranchToScale(proof.Hashes), nil } + +func (s *Syncer) getExecutionHeaderFromBeaconHeader(header api.BeaconHeaderWithExecutionHeaderResponse) (scale.VersionedExecutionPayloadHeader, []types.H256, error) { + var versionedExecutionPayloadHeader scale.VersionedExecutionPayloadHeader + var executionBranch []types.H256 + slot, err := strconv.ParseUint(header.Beacon.Slot, 10, 64) + if err != nil { + return versionedExecutionPayloadHeader, executionBranch, fmt.Errorf("invalid slot in header: %w", err) + } + if s.DenebForked(slot) { + executionPayloadScale, err := api.DenebJsonExecutionPayloadHeaderToScale(&header.Execution) + if err != nil { + return versionedExecutionPayloadHeader, executionBranch, fmt.Errorf("convert payloadHeader to scale: %w", err) + } + versionedExecutionPayloadHeader = scale.VersionedExecutionPayloadHeader{Deneb: &executionPayloadScale} + } else { + executionPayloadScale, err := api.CapellaJsonExecutionPayloadHeaderToScale(&header.Execution) + if err != nil { + return versionedExecutionPayloadHeader, executionBranch, fmt.Errorf("convert payloadHeader to scale: %w", err) + } + versionedExecutionPayloadHeader = scale.VersionedExecutionPayloadHeader{Capella: &executionPayloadScale} + } + if len(header.ExecutionBranch) == 0 { + return versionedExecutionPayloadHeader, executionBranch, fmt.Errorf("invalid ExecutionBranch in header: %w", err) + } + executionBranch = util.ProofBranchToScale(header.ExecutionBranch) + return versionedExecutionPayloadHeader, executionBranch, nil +} diff --git a/relayer/relays/beacon/state/beacon_deneb_encoding.go b/relayer/relays/beacon/state/beacon_deneb_encoding.go index e4b2e10a3..274323124 100644 --- a/relayer/relays/beacon/state/beacon_deneb_encoding.go +++ b/relayer/relays/beacon/state/beacon_deneb_encoding.go @@ -1,5 +1,5 @@ // Code generated by fastssz. DO NOT EDIT. -// Hash: bf79b1b8dfc6467c2ea96da7962e06ebfc35d247e27dae4aec667508dfc5d5c5 +// Hash: 00aea8602ff5e5fb2169a817d63f065398a715fcb79623b35af84d972c308644 // Version: 0.1.3 package state diff --git a/relayer/relays/beacon/state/beacon_encoding.go b/relayer/relays/beacon/state/beacon_encoding.go index 571446605..3ed880a78 100644 --- a/relayer/relays/beacon/state/beacon_encoding.go +++ b/relayer/relays/beacon/state/beacon_encoding.go @@ -1,5 +1,5 @@ // Code generated by fastssz. DO NOT EDIT. -// Hash: bf79b1b8dfc6467c2ea96da7962e06ebfc35d247e27dae4aec667508dfc5d5c5 +// Hash: 00aea8602ff5e5fb2169a817d63f065398a715fcb79623b35af84d972c308644 // Version: 0.1.3 package state diff --git a/relayer/relays/beacon/state/state.go b/relayer/relays/beacon/state/state.go index 9b22e7dd6..13bdf8e68 100644 --- a/relayer/relays/beacon/state/state.go +++ b/relayer/relays/beacon/state/state.go @@ -17,3 +17,10 @@ type FinalizedHeader struct { InitialCheckpointRoot common.Hash InitialCheckpointSlot uint64 } + +type CompactExecutionHeaderState struct { + ParentHash common.Hash + BlockNumber uint64 + StateRoot common.Hash + ReceiptsRoot common.Hash +} diff --git a/relayer/relays/execution/config.go b/relayer/relays/execution/config.go index f61e3f7c9..e35ddce7b 100644 --- a/relayer/relays/execution/config.go +++ b/relayer/relays/execution/config.go @@ -2,6 +2,7 @@ package execution import ( "github.com/snowfork/snowbridge/relayer/config" + BeaconConfigs "github.com/snowfork/snowbridge/relayer/relays/beacon/config" ) type Config struct { @@ -10,9 +11,10 @@ type Config struct { } type SourceConfig struct { - Ethereum config.EthereumConfig `mapstructure:"ethereum"` - Contracts ContractsConfig `mapstructure:"contracts"` - ChannelID ChannelID `mapstructure:"channel-id"` + Ethereum config.EthereumConfig `mapstructure:"ethereum"` + Contracts ContractsConfig `mapstructure:"contracts"` + ChannelID ChannelID `mapstructure:"channel-id"` + Beacon BeaconConfigs.BeaconConfig `mapstructure:"beacon"` } type ContractsConfig struct { diff --git a/relayer/relays/execution/main.go b/relayer/relays/execution/main.go index 0ea044879..cae88d748 100644 --- a/relayer/relays/execution/main.go +++ b/relayer/relays/execution/main.go @@ -16,6 +16,7 @@ import ( "github.com/snowfork/snowbridge/relayer/chain/parachain" "github.com/snowfork/snowbridge/relayer/contracts" "github.com/snowfork/snowbridge/relayer/crypto/sr25519" + "github.com/snowfork/snowbridge/relayer/relays/beacon/header" "golang.org/x/sync/errgroup" ) @@ -25,6 +26,7 @@ type Relay struct { paraconn *parachain.Connection ethconn *ethereum.Connection gatewayContract *contracts.Gateway + beaconHeader *header.Header } func NewRelay( @@ -78,6 +80,13 @@ func (r *Relay) Start(ctx context.Context, eg *errgroup.Group) error { } r.gatewayContract = contract + beaconHeader := header.New( + writer, + r.config.Source.Beacon.Endpoint, + r.config.Source.Beacon.Spec, + ) + r.beaconHeader = &beaconHeader + for { select { case <-ctx.Done(): @@ -145,6 +154,18 @@ func (r *Relay) Start(ctx context.Context, eg *errgroup.Group) error { logger.Warn("inbound message outdated, just skipped") continue } + nextBlockNumber := new(big.Int).SetUint64(ev.Raw.BlockNumber + 1) + + blockHeader, err := ethconn.Client().HeaderByNumber(ctx, nextBlockNumber) + if err != nil { + return fmt.Errorf("get block header: %w", err) + } + + // ParentBeaconRoot in https://eips.ethereum.org/EIPS/eip-4788 from Deneb onward + err = beaconHeader.SyncExecutionHeader(ctx, *blockHeader.ParentBeaconRoot) + if err != nil { + return fmt.Errorf("sync beacon header: %w", err) + } err = writer.WriteToParachainAndWatch(ctx, "EthereumInboundQueue.submit", inboundMsg) if err != nil { diff --git a/web/packages/test/config/execution-relay.json b/web/packages/test/config/execution-relay.json index 91ffe603d..11d754db1 100644 --- a/web/packages/test/config/execution-relay.json +++ b/web/packages/test/config/execution-relay.json @@ -6,7 +6,15 @@ "contracts": { "Gateway": null }, - "channel-id": null + "channel-id": null, + "beacon": { + "endpoint": "http://127.0.0.1:9596", + "spec": { + "slotsInEpoch": 32, + "epochsPerSyncCommitteePeriod": 256, + "denebForkedEpoch": 0 + } + } }, "sink": { "parachain": {