diff --git a/.gitignore b/.gitignore index 9e3c9ed722..033ddab5c5 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ contracts/beefy-state.json go/ gocache/ +go.work* +control/target/ diff --git a/relayer/chain/ethereum/header.go b/relayer/chain/ethereum/header.go index b3600a8fb3..f9a5628732 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 8e35ff98ac..1838183645 100644 --- a/relayer/chain/parachain/writer.go +++ b/relayer/chain/parachain/writer.go @@ -266,31 +266,6 @@ func (wr *ParachainWriter) GetLastBasicChannelNonceByAddress(address common.Addr return uint64(nonce), nil } -func (wr *ParachainWriter) GetLastExecutionHeaderState() (state.ExecutionHeader, error) { - key, err := types.CreateStorageKey(wr.conn.Metadata(), "EthereumBeaconClient", "LatestExecutionState", nil, nil) - if err != nil { - return state.ExecutionHeader{}, fmt.Errorf("create storage key for LatestExecutionHeaderState: %w", err) - } - - var storageState struct { - BeaconBlockRoot types.H256 - BeaconSlot types.U64 - BlockHash types.H256 - BlockNumber types.U64 - } - _, err = wr.conn.API().RPC.State.GetStorageLatest(key, &storageState) - if err != nil { - return state.ExecutionHeader{}, fmt.Errorf("get storage for LatestExecutionHeaderState (err): %w", err) - } - - return state.ExecutionHeader{ - BeaconBlockRoot: common.Hash(storageState.BeaconBlockRoot), - BeaconSlot: uint64(storageState.BeaconSlot), - BlockHash: common.Hash(storageState.BlockHash), - BlockNumber: uint64(storageState.BlockNumber), - }, nil -} - func (wr *ParachainWriter) GetLastFinalizedHeaderState() (state.FinalizedHeader, error) { finalizedState, err := wr.GetFinalizedStateByStorageKey("LatestFinalizedBlockRoot") if err != nil { @@ -385,3 +360,91 @@ 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 +} + +func (wr *ParachainWriter) GetLastFinalizedStateIndex() (types.U32, error) { + var index types.U32 + key, err := types.CreateStorageKey(wr.conn.Metadata(), "EthereumBeaconClient", "FinalizedBeaconStateIndex", nil, nil) + if err != nil { + return index, fmt.Errorf("create storage key for FinalizedBeaconStateIndex: %w", err) + } + + _, err = wr.conn.API().RPC.State.GetStorageLatest(key, &index) + if err != nil { + return index, fmt.Errorf("get storage for FinalizedBeaconStateIndex (err): %w", err) + } + + return index, nil +} + +func (wr *ParachainWriter) GetFinalizedBeaconRootByIndex(index uint32) (types.H256, error) { + var beaconRoot types.H256 + encodedIndex, err := types.EncodeToBytes(types.NewU32(index)) + if err != nil { + return beaconRoot, fmt.Errorf("get finalized beacon root encode index error: %w", err) + } + key, err := types.CreateStorageKey(wr.conn.Metadata(), "EthereumBeaconClient", "FinalizedBeaconStateMapping", encodedIndex, nil) + if err != nil { + return beaconRoot, fmt.Errorf("create storage key for FinalizedBeaconStateMapping: %w", err) + } + + _, err = wr.conn.API().RPC.State.GetStorageLatest(key, &beaconRoot) + if err != nil { + return beaconRoot, fmt.Errorf("get storage for FinalizedBeaconStateMapping (err): %w", err) + } + + return beaconRoot, nil +} + +func (wr *ParachainWriter) FindCheckPointBackward(slot uint64) (state.FinalizedHeader, error) { + var beaconState state.FinalizedHeader + lastIndex, err := wr.GetLastFinalizedStateIndex() + if err != nil { + return beaconState, fmt.Errorf("GetLastFinalizedStateIndex error: %w", err) + } + startIndex := uint32(lastIndex) + endIndex := uint32(0) + if lastIndex > 256 { + endIndex = endIndex - 256 + } + for index := startIndex; index >= endIndex; index-- { + beaconRoot, err := wr.GetFinalizedBeaconRootByIndex(index) + if err != nil { + return beaconState, fmt.Errorf("GetFinalizedBeaconRootByIndex %d, error: %w", index, err) + } + beaconState, err = wr.GetFinalizedHeaderStateByBlockRoot(beaconRoot) + if err != nil { + return beaconState, fmt.Errorf("GetFinalizedHeaderStateByBlockRoot %s, error: %w", beaconRoot.Hex(), err) + } + if beaconState.BeaconSlot < slot { + break + } + if beaconState.BeaconSlot > slot && beaconState.BeaconSlot < slot+8192 { + break + } + } + if beaconState.BeaconSlot > slot && beaconState.BeaconSlot < slot+8192 { + return beaconState, nil + } + return beaconState, fmt.Errorf("Can't find checkpoint on chain for slot %d", slot) +} diff --git a/relayer/cmd/generate_beacon_data.go b/relayer/cmd/generate_beacon_data.go index a6eecb998c..6f480dd1c9 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 export-json flag: %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/cache/cache.go b/relayer/relays/beacon/cache/cache.go index 1033a7a599..cd366148fc 100644 --- a/relayer/relays/beacon/cache/cache.go +++ b/relayer/relays/beacon/cache/cache.go @@ -67,14 +67,6 @@ func (b *BeaconCache) SetLastSyncedFinalizedState(finalizedHeaderRoot common.Has } } -func (b *BeaconCache) SetLastSyncedExecutionSlot(slot uint64) { - b.mu.Lock() - defer b.mu.Unlock() - if slot > b.LastSyncedExecutionSlot { - b.LastSyncedExecutionSlot = slot - } -} - func (b *BeaconCache) SetInitialCheckpointSlot(slot uint64) { b.mu.Lock() defer b.mu.Unlock() diff --git a/relayer/relays/beacon/header/header.go b/relayer/relays/beacon/header/header.go index 1b1d56f902..f12f59831e 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" @@ -22,6 +23,7 @@ var ErrFinalizedHeaderNotImported = errors.New("finalized header not imported") var ErrSyncCommitteeNotImported = errors.New("sync committee not imported") var ErrSyncCommitteeLatency = errors.New("sync committee latency found") var ErrExecutionHeaderNotImported = errors.New("execution header not imported") +var ErrBeaconHeaderNotFinalized = errors.New("beacon header not finalized") type Header struct { cache *cache.BeaconCache @@ -43,22 +45,15 @@ func (h *Header) Sync(ctx context.Context, eg *errgroup.Group) error { return fmt.Errorf("fetch parachain last finalized header state: %w", err) } latestSyncedPeriod := h.syncer.ComputeSyncPeriodAtSlot(lastFinalizedHeaderState.BeaconSlot) - executionHeaderState, err := h.writer.GetLastExecutionHeaderState() - if err != nil { - return fmt.Errorf("fetch last execution hash: %w", err) - } log.WithFields(log.Fields{ "last_finalized_hash": lastFinalizedHeaderState.BeaconBlockRoot, "last_finalized_slot": lastFinalizedHeaderState.BeaconSlot, "last_finalized_period": latestSyncedPeriod, - "last_execution_hash": executionHeaderState.BeaconBlockRoot, - "last_execution_slot": executionHeaderState.BeaconSlot, }).Info("set cache: Current state") h.cache.SetLastSyncedFinalizedState(lastFinalizedHeaderState.BeaconBlockRoot, lastFinalizedHeaderState.BeaconSlot) h.cache.SetInitialCheckpointSlot(lastFinalizedHeaderState.InitialCheckpointSlot) h.cache.AddCheckPointSlots([]uint64{lastFinalizedHeaderState.BeaconSlot}) - h.cache.SetLastSyncedExecutionSlot(executionHeaderState.BeaconSlot) log.Info("starting to sync finalized headers") @@ -186,11 +181,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 +198,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() { @@ -346,39 +269,57 @@ func (h *Header) populateFinalizedCheckpoint(slot uint64) error { } func (h *Header) populateClosestCheckpoint(slot uint64) (cache.Proof, error) { + var checkpoint cache.Proof checkpoint, err := h.cache.GetClosestCheckpoint(slot) switch { case errors.Is(cache.FinalizedCheckPointNotAvailable, err) || errors.Is(cache.FinalizedCheckPointNotPopulated, err): checkpointSlot := checkpoint.Slot if checkpointSlot == 0 { - checkpointSlot = h.syncer.CalculateNextCheckpointSlot(slot) log.WithFields(log.Fields{"calculatedCheckpointSlot": checkpointSlot}).Info("checkpoint slot not available, try with slot in next sync period instead") + checkpointSlot = h.syncer.CalculateNextCheckpointSlot(slot) + lastFinalizedHeaderState, err := h.writer.GetLastFinalizedHeaderState() + if err != nil { + return checkpoint, fmt.Errorf("get last finalized header for the checkpoint: %w", err) + } + if slot > lastFinalizedHeaderState.BeaconSlot { + return checkpoint, ErrBeaconHeaderNotFinalized + } + if checkpointSlot < lastFinalizedHeaderState.BeaconSlot { + log.WithFields(log.Fields{"calculatedCheckpointSlot": checkpointSlot, "lastFinalizedSlot": lastFinalizedHeaderState.BeaconSlot}).Info("fetch checkpoint on chain backward from history") + historyState, err := h.writer.FindCheckPointBackward(slot) + if err != nil { + return checkpoint, fmt.Errorf("get history finalized header for the checkpoint: %w", err) + } + checkpointSlot = historyState.BeaconSlot + } else { + log.WithFields(log.Fields{"calculatedCheckpointSlot": checkpointSlot, "lastFinalizedSlot": lastFinalizedHeaderState.BeaconSlot}).Info("calculated checkpoint slot should not be in the future, switch to the last finalized") + checkpointSlot = lastFinalizedHeaderState.BeaconSlot + } } err := h.populateFinalizedCheckpoint(checkpointSlot) if err != nil { - return cache.Proof{}, fmt.Errorf("populate closest checkpoint: %w", err) + return checkpoint, fmt.Errorf("populate closest checkpoint: %w", err) } log.Info("populated finalized checkpoint") checkpoint, err = h.cache.GetClosestCheckpoint(slot) if err != nil { - return cache.Proof{}, fmt.Errorf("get closest checkpoint after populating finalized header: %w", err) + return checkpoint, fmt.Errorf("get closest checkpoint after populating finalized header: %w", err) } log.WithFields(log.Fields{"slot": slot, "checkpoint": checkpoint}).Info("checkpoint after populating finalized header") return checkpoint, nil case err != nil: - return cache.Proof{}, fmt.Errorf("get closest checkpoint: %w", err) + return checkpoint, fmt.Errorf("get closest checkpoint: %w", err) } 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 +335,48 @@ 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) + lastFinalizedHeaderState, err := h.writer.GetLastFinalizedHeaderState() if err != nil { - return err + return fmt.Errorf("fetch last finalized header state: %w", err) + } + if header.Slot > lastFinalizedHeaderState.BeaconSlot { + return ErrBeaconHeaderNotFinalized + } + headerUpdate, err := h.getHeaderUpdateBySlot(header.Slot) + if err != nil { + return fmt.Errorf("get header update by slot with ancestry proof: %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 { + log.WithFields(log.Fields{"blockRoot": blockRoot.Hex(), "blockHash": blockHash.Hex(), "blockNumber": compactExecutionHeaderState.BlockNumber}).Info("ExecutionHeaderState already exist") + return nil + } + err = h.writer.WriteToParachainAndWatch(ctx, "EthereumBeaconClient.submit_execution_header", headerUpdate) + if err != nil { + return fmt.Errorf("submit_execution_header: %w", err) + } + 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("execution header did not sync successfully") } return nil } @@ -411,24 +386,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 1c87de7ec1..8cec45e319 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 f95a71232b..3ebe17f982 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 ff74a69ba2..00fb06ed87 100644 --- a/relayer/relays/beacon/header/syncer/api/api_response.go +++ b/relayer/relays/beacon/header/syncer/api/api_response.go @@ -1039,3 +1039,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 e53f6e467b..cda9afae1a 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 7df531f110..1c05aed0eb 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 6eb428af41..20cc9b7195 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 e373322105..a78a7f017a 100644 --- a/relayer/relays/beacon/header/syncer/scale/beacon_scale.go +++ b/relayer/relays/beacon/header/syncer/scale/beacon_scale.go @@ -318,3 +318,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 9e59e10676..69b0baa4a1 100644 --- a/relayer/relays/beacon/header/syncer/scale/json_conversion.go +++ b/relayer/relays/beacon/header/syncer/scale/json_conversion.go @@ -65,21 +65,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/sync_protocol.go b/relayer/relays/beacon/header/syncer/sync_protocol.go index 202fce9b46..085c5718ea 100644 --- a/relayer/relays/beacon/header/syncer/sync_protocol.go +++ b/relayer/relays/beacon/header/syncer/sync_protocol.go @@ -26,3 +26,7 @@ func (s *Syncer) CalculateNextCheckpointSlot(slot uint64) uint64 { func (s *Syncer) DenebForked(slot uint64) bool { return s.ComputeEpochAtSlot(slot) >= s.setting.DenebForkEpoch } + +func (s *Syncer) SyncPeriodLength() uint64 { + return s.setting.SlotsInEpoch * s.setting.EpochsPerSyncCommitteePeriod +} diff --git a/relayer/relays/beacon/header/syncer/syncer.go b/relayer/relays/beacon/header/syncer/syncer.go index af91246bea..affa1431ba 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, @@ -352,7 +356,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) diff --git a/relayer/relays/beacon/state/beacon_deneb_encoding.go b/relayer/relays/beacon/state/beacon_deneb_encoding.go index e4b2e10a34..274323124a 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 571446605c..3ed880a78c 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 9b22e7dd66..13bdf8e683 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 f61e3f7c94..1f97b635fc 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" + beaconconf "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 beaconconf.BeaconConfig `mapstructure:"beacon"` } type ContractsConfig struct { diff --git a/relayer/relays/execution/main.go b/relayer/relays/execution/main.go index 0ea044879b..2d55a2fc99 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,49 +80,48 @@ 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(): return nil - case <-time.After(12 * time.Second): + case <-time.After(6 * time.Second): log.WithFields(log.Fields{ "channelId": r.config.Source.ChannelID, }).Info("Polling Nonces") - executionHeaderState, err := writer.GetLastExecutionHeaderState() - if err != nil { - return err - } - paraNonce, err := r.fetchLatestParachainNonce() if err != nil { return err } - if executionHeaderState.BlockNumber == 0 { - log.WithFields(log.Fields{ - "channelId": r.config.Source.ChannelID, - }).Info("Beacon execution state syncing not started, waiting...") - continue - } - - ethNonce, err := r.fetchEthereumNonce(ctx, executionHeaderState.BlockNumber) + ethNonce, err := r.fetchEthereumNonce(ctx) if err != nil { return err } log.WithFields(log.Fields{ - "ethBlockNumber": executionHeaderState.BlockNumber, - "channelId": types.H256(r.config.Source.ChannelID).Hex(), - "paraNonce": paraNonce, - "ethNonce": ethNonce, + "channelId": types.H256(r.config.Source.ChannelID).Hex(), + "paraNonce": paraNonce, + "ethNonce": ethNonce, }).Info("Polled Nonces") if paraNonce == ethNonce { continue } - events, err := r.findEvents(ctx, executionHeaderState.BlockNumber, paraNonce+1) + blockNumber, err := ethconn.Client().BlockNumber(ctx) + if err != nil { + return fmt.Errorf("get last block number: %w", err) + } + + events, err := r.findEvents(ctx, blockNumber, paraNonce+1) if err != nil { return fmt.Errorf("find events: %w", err) } @@ -145,6 +146,22 @@ 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 == header.ErrBeaconHeaderNotFinalized { + logger.Warn("beacon header not finalized, just skipped") + continue + } + if err != nil { + return fmt.Errorf("sync beacon header: %w", err) + } err = writer.WriteToParachainAndWatch(ctx, "EthereumInboundQueue.submit", inboundMsg) if err != nil { @@ -187,11 +204,9 @@ func (r *Relay) fetchLatestParachainNonce() (uint64, error) { return paraNonce, nil } -func (r *Relay) fetchEthereumNonce(ctx context.Context, blockNumber uint64) (uint64, error) { +func (r *Relay) fetchEthereumNonce(ctx context.Context) (uint64, error) { opts := bind.CallOpts{ - Pending: false, - BlockNumber: new(big.Int).SetUint64(blockNumber), - Context: ctx, + Context: ctx, } _, ethOutboundNonce, err := r.gatewayContract.ChannelNoncesOf(&opts, r.config.Source.ChannelID) if err != nil { diff --git a/web/packages/test/config/execution-relay.json b/web/packages/test/config/execution-relay.json index 91ffe603de..11d754db16 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": {