diff --git a/relayer/cmd/generate_beacon_data.go b/relayer/cmd/generate_beacon_data.go index dfa13e860a..5b93a47b8c 100644 --- a/relayer/cmd/generate_beacon_data.go +++ b/relayer/cmd/generate_beacon_data.go @@ -41,8 +41,10 @@ func generateBeaconFixtureCmd() *cobra.Command { } cmd.Flags().String("config", "/tmp/snowbridge/beacon-relay.json", "Path to the beacon relay config") - cmd.Flags().Bool("wait_until_next_period", true, "Waiting until next period") + cmd.Flags().String("execution-config", "/tmp/snowbridge/execution-relay-asset-hub.json", "Path to the execution relay config") + cmd.Flags().Bool("wait-until-next-period", true, "Waiting until next period") cmd.Flags().Uint32("nonce", 1, "Nonce of the inbound message") + cmd.Flags().Bool("finality-update-only", false, "Generate finality update only") return cmd } @@ -83,9 +85,9 @@ func generateInboundFixtureCmd() *cobra.Command { } cmd.Flags().String("beacon-config", "/tmp/snowbridge/beacon-relay.json", "Path to the beacon relay config") - cmd.Flags().String("execution-config", "/tmp/snowbridge/execution-relay-asset-hub.json", "Path to the beacon relay config") + cmd.Flags().String("execution-config", "/tmp/snowbridge/execution-relay-asset-hub.json", "Path to the execution relay config") cmd.Flags().Uint32("nonce", 1, "Nonce of the inbound message") - cmd.Flags().String("test_case", "register_token", "Inbound test case") + cmd.Flags().String("test-case", "register_token", "Inbound test case") return cmd } @@ -106,7 +108,7 @@ type InboundFixture struct { const ( pathToBeaconTestFixtureFiles = "polkadot-sdk/bridges/snowbridge/pallets/ethereum-client/tests/fixtures" pathToInboundQueueFixtureTemplate = "polkadot-sdk/bridges/snowbridge/templates/beacon-fixtures.mustache" - pathToInboundQueueFixtureData = "polkadot-sdk/bridges/snowbridge/pallets/ethereum-client/fixtures/src/lib.rs" + pathToEthereumClientFixtureData = "polkadot-sdk/bridges/snowbridge/pallets/ethereum-client/fixtures/src/lib.rs" pathToInboundQueueFixtureTestCaseTemplate = "polkadot-sdk/bridges/snowbridge/templates/inbound-fixtures.mustache" pathToInboundQueueFixtureTestCaseData = "polkadot-sdk/bridges/snowbridge/pallets/inbound-queue/fixtures/src/%s.rs" ) @@ -206,8 +208,11 @@ func generateBeaconTestFixture(cmd *cobra.Command, _ []string) error { client := api.NewBeaconClient(conf.Source.Beacon.Endpoint, conf.Source.Beacon.StateEndpoint) s := syncer.New(client, &store, p) - viper.SetConfigFile("/tmp/snowbridge/execution-relay-asset-hub.json") - + executionConfigFile, err := cmd.Flags().GetString("execution-config") + if err != nil { + return err + } + viper.SetConfigFile(executionConfigFile) if err = viper.ReadInConfig(); err != nil { return err } @@ -261,91 +266,104 @@ func generateBeaconTestFixture(cmd *cobra.Command, _ []string) error { } log.Info("created sync committee update file") - // get inbound message data start - channelID := executionConfig.Source.ChannelID - address := common.HexToAddress(executionConfig.Source.Contracts.Gateway) - gatewayContract, err := contracts.NewGateway(address, ethconn.Client()) - if err != nil { - return err - } - nonce, err := cmd.Flags().GetUint32("nonce") - if err != nil { - return err - } - event, err := getEthereumEvent(ctx, gatewayContract, channelID, nonce) - if err != nil { - return err - } - receiptTrie, err := headerCache.GetReceiptTrie(ctx, event.Raw.BlockHash) - if err != nil { - return err - } - inboundMessage, err := ethereum.MakeMessageFromEvent(&event.Raw, receiptTrie) - if err != nil { - return err - } - messageBlockNumber := event.Raw.BlockNumber + var headerUpdate beaconjson.HeaderUpdate + var messageJSON parachain.MessageJSON + var beaconFinalizedUpdate *scale.Update - log.WithFields(log.Fields{ - "message": inboundMessage, - "blockHash": event.Raw.BlockHash.Hex(), - "blockNumber": messageBlockNumber, - }).Info("event is at block") + finalityUpdateOnly, err := cmd.Flags().GetBool("finality-update-only") + if finalityUpdateOnly { + beaconFinalizedUpdate, err = getFinalizedUpdate(*s, syncCommitteeUpdate.FinalizedHeader.Slot+1) + if err != nil { + return err + } + } else { + // get inbound message data start + channelID := executionConfig.Source.ChannelID + address := common.HexToAddress(executionConfig.Source.Contracts.Gateway) + gatewayContract, err := contracts.NewGateway(address, ethconn.Client()) + if err != nil { + return err + } + nonce, err := cmd.Flags().GetUint32("nonce") + if err != nil { + return err + } + event, err := getEthereumEvent(ctx, gatewayContract, channelID, nonce) + if err != nil { + return err + } + receiptTrie, err := headerCache.GetReceiptTrie(ctx, event.Raw.BlockHash) + if err != nil { + return err + } + inboundMessage, err := ethereum.MakeMessageFromEvent(&event.Raw, receiptTrie) + if err != nil { + return err + } + messageBlockNumber := event.Raw.BlockNumber - finalizedUpdateAfterMessage, err := getFinalizedUpdate(*s, messageBlockNumber) - if err != nil { - return err - } + log.WithFields(log.Fields{ + "message": inboundMessage, + "blockHash": event.Raw.BlockHash.Hex(), + "blockNumber": messageBlockNumber, + }).Info("event is at block") - finalizedHeaderSlot := uint64(finalizedUpdateAfterMessage.Payload.FinalizedHeader.Slot) + beaconFinalizedUpdate, err = getFinalizedUpdate(*s, messageBlockNumber) + if err != nil { + return err + } - beaconBlock, blockNumber, err := getBeaconBlockContainingExecutionHeader(*s, messageBlockNumber, finalizedHeaderSlot) - if err != nil { - return fmt.Errorf("get beacon block containing header: %w", err) - } + finalizedHeaderSlot := uint64(beaconFinalizedUpdate.Payload.FinalizedHeader.Slot) - beaconBlockSlot, err := strconv.ParseUint(beaconBlock.Data.Message.Slot, 10, 64) - if err != nil { - return err - } + beaconBlock, blockNumber, err := getBeaconBlockContainingExecutionHeader(*s, messageBlockNumber, finalizedHeaderSlot) + if err != nil { + return fmt.Errorf("get beacon block containing header: %w", err) + } - if blockNumber == messageBlockNumber { - log.WithFields(log.Fields{ - "slot": beaconBlock.Data.Message.Slot, - "blockHash": beaconBlock.Data.Message.Body.ExecutionPayload.BlockHash, - "blockNumber": blockNumber, - }).WithError(err).Info("found execution header containing event") - } + beaconBlockSlot, err := strconv.ParseUint(beaconBlock.Data.Message.Slot, 10, 64) + if err != nil { + return err + } - checkPoint := cache.Proof{ - FinalizedBlockRoot: finalizedUpdateAfterMessage.FinalizedHeaderBlockRoot, - BlockRootsTree: finalizedUpdateAfterMessage.BlockRootsTree, - Slot: uint64(finalizedUpdateAfterMessage.Payload.FinalizedHeader.Slot), - } - headerUpdateScale, err := s.GetHeaderUpdateBySlotWithCheckpoint(beaconBlockSlot, &checkPoint) - if err != nil { - return fmt.Errorf("get header update: %w", err) - } - inboundMessage.Proof.ExecutionProof = headerUpdateScale - headerUpdate := headerUpdateScale.ToJSON() + if blockNumber == messageBlockNumber { + log.WithFields(log.Fields{ + "slot": beaconBlock.Data.Message.Slot, + "blockHash": beaconBlock.Data.Message.Body.ExecutionPayload.BlockHash, + "blockNumber": blockNumber, + }).WithError(err).Info("found execution header containing event") + } - log.WithField("blockNumber", blockNumber).Info("found beacon block by slot") + checkPoint := cache.Proof{ + FinalizedBlockRoot: beaconFinalizedUpdate.FinalizedHeaderBlockRoot, + BlockRootsTree: beaconFinalizedUpdate.BlockRootsTree, + Slot: uint64(beaconFinalizedUpdate.Payload.FinalizedHeader.Slot), + } + headerUpdateScale, err := s.GetHeaderUpdateBySlotWithCheckpoint(beaconBlockSlot, &checkPoint) + if err != nil { + return fmt.Errorf("get header update: %w", err) + } + inboundMessage.Proof.ExecutionProof = headerUpdateScale + headerUpdate = headerUpdateScale.ToJSON() - messageJSON := inboundMessage.ToJSON() + log.WithField("blockNumber", blockNumber).Info("found beacon block by slot") - err = writeJSONToFile(headerUpdate, fmt.Sprintf("%s/%s", pathToBeaconTestFixtureFiles, "execution-proof.json")) - if err != nil { - return err - } - log.Info("created execution update file") - err = writeJSONToFile(messageJSON, fmt.Sprintf("%s/%s", pathToBeaconTestFixtureFiles, "inbound-message.json")) - if err != nil { - return err - } - log.Info("created inbound message file") - // get inbound message data end + messageJSON = inboundMessage.ToJSON() - finalizedUpdate := finalizedUpdateAfterMessage.Payload.ToJSON() + err = writeJSONToFile(headerUpdate, fmt.Sprintf("%s/%s", pathToBeaconTestFixtureFiles, "execution-proof.json")) + if err != nil { + return err + } + log.Info("created execution update file") + err = writeJSONToFile(messageJSON, fmt.Sprintf("%s/%s", pathToBeaconTestFixtureFiles, "inbound-message.json")) + if err != nil { + return err + } + log.Info("created inbound message file") + // get inbound message data end + headerUpdate.RemoveLeadingZeroHashes() + messageJSON.RemoveLeadingZeroHashes() + } + finalizedUpdate := beaconFinalizedUpdate.Payload.ToJSON() if finalizedUpdate.AttestedHeader.Slot <= initialSyncHeaderSlot { return fmt.Errorf("AttestedHeader slot should be greater than initialSyncHeaderSlot") } @@ -371,8 +389,6 @@ func generateBeaconTestFixture(cmd *cobra.Command, _ []string) error { initialSync.RemoveLeadingZeroHashes() syncCommitteeUpdate.RemoveLeadingZeroHashes() finalizedUpdate.RemoveLeadingZeroHashes() - headerUpdate.RemoveLeadingZeroHashes() - messageJSON.RemoveLeadingZeroHashes() data := Data{ CheckpointUpdate: initialSync, @@ -388,15 +404,15 @@ func generateBeaconTestFixture(cmd *cobra.Command, _ []string) error { return fmt.Errorf("render inbound queue benchmark fixture: %w", err) } log.WithFields(log.Fields{ - "location": pathToInboundQueueFixtureData, + "location": pathToEthereumClientFixtureData, }).Info("writing result file") - err = writeRawDataFile(fmt.Sprintf("%s", pathToInboundQueueFixtureData), rendered) + err = writeRawDataFile(fmt.Sprintf("%s", pathToEthereumClientFixtureData), rendered) if err != nil { return err } // Generate test fixture in next period (require waiting a long time) - waitUntilNextPeriod, err := cmd.Flags().GetBool("wait_until_next_period") + waitUntilNextPeriod, err := cmd.Flags().GetBool("wait-until-next-period") if waitUntilNextPeriod { log.Info("waiting finalized_update in next period (5 hours later), be patient and wait...") for { @@ -537,11 +553,6 @@ func generateExecutionUpdate(cmd *cobra.Command, _ []string) error { return nil } -func generateInboundTestFixture(ctx context.Context, beaconEndpoint string) error { - - return nil -} - func getEthereumEvent(ctx context.Context, gatewayContract *contracts.Gateway, channelID executionConf.ChannelID, nonce uint32) (*contracts.GatewayOutboundMessageAccepted, error) { maxBlockNumber := uint64(10000) @@ -809,7 +820,7 @@ func generateInboundFixture(cmd *cobra.Command, _ []string) error { messageJSON.RemoveLeadingZeroHashes() // writing inbound fixture by test case - testCase, err := cmd.Flags().GetString("test_case") + testCase, err := cmd.Flags().GetString("test-case") if err != nil { return err } diff --git a/relayer/relays/beacon/header/header_test.go b/relayer/relays/beacon/header/header_test.go index 210fa16f69..d83c50ae01 100644 --- a/relayer/relays/beacon/header/header_test.go +++ b/relayer/relays/beacon/header/header_test.go @@ -2,6 +2,8 @@ package header import ( "context" + "testing" + "github.com/ethereum/go-ethereum/common" "github.com/snowfork/snowbridge/relayer/relays/beacon/config" "github.com/snowfork/snowbridge/relayer/relays/beacon/header/syncer/api" @@ -11,7 +13,6 @@ import ( "github.com/snowfork/snowbridge/relayer/relays/beacon/store" "github.com/snowfork/snowbridge/relayer/relays/testutil" "github.com/stretchr/testify/require" - "testing" ) // Verifies that the closest checkpoint is populated successfully if it is not populated in the first place. @@ -43,6 +44,7 @@ func TestSyncInterimFinalizedUpdate_WithDataFromAPI(t *testing.T) { common.HexToHash("0x5119c1f71943a3eea34ddc48c7fe399d4b66f939350036431847ed0913448749"): headerAtSlot4571072, } client.BlocksAtSlot = map[uint64]api.BeaconBlockResponse{ + 4571072: blockAtSlot4571137, 4571137: blockAtSlot4571137, } @@ -100,6 +102,7 @@ func TestSyncInterimFinalizedUpdate_WithDataFromStore(t *testing.T) { common.HexToHash("0x5119c1f71943a3eea34ddc48c7fe399d4b66f939350036431847ed0913448749"): headerAtSlot4571072, } client.BlocksAtSlot = map[uint64]api.BeaconBlockResponse{ + 4571072: blockAtSlot4571137, 4571137: blockAtSlot4571137, } @@ -164,7 +167,9 @@ func TestSyncInterimFinalizedUpdate_WithDataFromStoreWithDifferentBlocks(t *test client.Header = map[common.Hash]api.BeaconHeader{ common.HexToHash("0x968a372336b4e08a6bbd25e9f31b336d322ede1e5c70763f61d2241ad3d66d36"): headerAtSlot4570752, } + blockAtSlot4571137, err := testutil.GetBlockAtSlot(4571137) client.BlocksAtSlot = map[uint64]api.BeaconBlockResponse{ + 4570752: blockAtSlot4571137, 4570818: blockAtSlot4570818, } diff --git a/relayer/relays/beacon/header/syncer/api/api_deneb.go b/relayer/relays/beacon/header/syncer/api/api_deneb.go index 3ebe17f982..efa95da21f 100644 --- a/relayer/relays/beacon/header/syncer/api/api_deneb.go +++ b/relayer/relays/beacon/header/syncer/api/api_deneb.go @@ -5,7 +5,6 @@ import ( "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" @@ -55,7 +54,7 @@ func DenebExecutionPayloadToScale(e *state.ExecutionPayloadDeneb) (scale.Executi }, nil } -func DenebJsonExecutionPayloadHeaderToScale(e *beaconjson.FullExecutionPayloadHeaderJson) (scale.ExecutionPayloadHeaderDeneb, error) { +func DenebJsonExecutionPayloadHeaderToScale(e ExecutionHeaderResponse) (scale.ExecutionPayloadHeaderDeneb, error) { var executionPayloadHeader scale.ExecutionPayloadHeaderDeneb var baseFeePerGas big.Int baseFeePerGasU64, err := util.ToUint64(e.BaseFeePerGas) diff --git a/relayer/relays/beacon/header/syncer/api/api_response.go b/relayer/relays/beacon/header/syncer/api/api_response.go index 1ba06034b7..79e47840d9 100644 --- a/relayer/relays/beacon/header/syncer/api/api_response.go +++ b/relayer/relays/beacon/header/syncer/api/api_response.go @@ -21,7 +21,9 @@ type SyncCommitteePeriodUpdateResponse struct { NextSyncCommittee SyncCommitteeResponse `json:"next_sync_committee"` NextSyncCommitteeBranch []string `json:"next_sync_committee_branch"` FinalizedHeader struct { - Beacon HeaderResponse `json:"beacon"` + Beacon HeaderResponse `json:"beacon"` + ExecutionHeader ExecutionHeaderResponse `json:"execution"` + ExecutionBranch []string `json:"execution_branch"` } `json:"finalized_header"` FinalityBranch []string `json:"finality_branch"` SyncAggregate SyncAggregateResponse `json:"sync_aggregate"` @@ -48,36 +50,38 @@ type BeaconBlockResponseBody struct { DepositCount string `json:"deposit_count"` BlockHash string `json:"block_hash"` } `json:"eth1_data"` - Graffiti string `json:"graffiti"` - ProposerSlashings []ProposerSlashingResponse `json:"proposer_slashings"` - AttesterSlashings []AttesterSlashingResponse `json:"attester_slashings"` - Attestations []AttestationResponse `json:"attestations"` - Deposits []DepositResponse `json:"deposits"` - VoluntaryExits []SignedVoluntaryExitResponse `json:"voluntary_exits"` - SyncAggregate SyncAggregateResponse `json:"sync_aggregate"` - ExecutionPayload 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"` - Transactions []string `json:"transactions"` - Withdrawals []WithdrawalResponse `json:"withdrawals"` - BlobGasUsed string `json:"blob_gas_used,omitempty"` - ExcessBlobGas string `json:"excess_blob_gas,omitempty"` - } `json:"execution_payload"` + Graffiti string `json:"graffiti"` + ProposerSlashings []ProposerSlashingResponse `json:"proposer_slashings"` + AttesterSlashings []AttesterSlashingResponse `json:"attester_slashings"` + Attestations []AttestationResponse `json:"attestations"` + Deposits []DepositResponse `json:"deposits"` + VoluntaryExits []SignedVoluntaryExitResponse `json:"voluntary_exits"` + SyncAggregate SyncAggregateResponse `json:"sync_aggregate"` + ExecutionPayload ExecutionPayload `json:"execution_payload"` BlsToExecutionChanges []SignedBLSToExecutionChangeResponse `json:"bls_to_execution_changes"` BlobKzgCommitments []string `json:"blob_kzg_commitments"` } +type ExecutionPayload 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"` + Transactions []string `json:"transactions"` + Withdrawals []WithdrawalResponse `json:"withdrawals"` + BlobGasUsed string `json:"blob_gas_used,omitempty"` + ExcessBlobGas string `json:"excess_blob_gas,omitempty"` +} + type BeaconBlockResponse struct { Data BeaconBlockResponseData `json:"data"` } @@ -170,6 +174,26 @@ type HeaderResponse struct { BodyRoot string `json:"body_root"` } +type ExecutionHeaderResponse 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"` +} + type SyncCommitteeResponse struct { Pubkeys []string `json:"pubkeys"` AggregatePubkey string `json:"aggregate_pubkey"` @@ -271,7 +295,9 @@ type LatestFinalisedUpdateResponse struct { Beacon HeaderResponse `json:"beacon"` } `json:"attested_header"` FinalizedHeader struct { - Beacon HeaderResponse `json:"beacon"` + Beacon HeaderResponse `json:"beacon"` + ExecutionHeader ExecutionHeaderResponse `json:"execution"` + ExecutionBranch []string `json:"execution_branch"` } `json:"finalized_header"` FinalityBranch []string `json:"finality_branch"` SyncAggregate SyncAggregateResponse `json:"sync_aggregate"` @@ -1046,7 +1072,7 @@ func CapellaExecutionPayloadToScale(e *state.ExecutionPayloadCapella) (scale.Exe }, nil } -func CapellaJsonExecutionPayloadHeaderToScale(e *beaconjson.FullExecutionPayloadHeaderJson) (scale.ExecutionPayloadHeaderCapella, error) { +func CapellaJsonExecutionPayloadHeaderToScale(e ExecutionHeaderResponse) (scale.ExecutionPayloadHeaderCapella, error) { var executionPayloadHeader scale.ExecutionPayloadHeaderCapella var baseFeePerGas big.Int baseFeePerGasU64, err := util.ToUint64(e.BaseFeePerGas) @@ -1088,3 +1114,78 @@ func CapellaJsonExecutionPayloadHeaderToScale(e *beaconjson.FullExecutionPayload WithdrawalsRoot: types.NewH256(common.HexToHash(e.WithdrawalsRoot).Bytes()), }, nil } + +func (e ExecutionPayload) ToExecutionHeaderResponse() (ExecutionHeaderResponse, error) { + var payloadHeader ExecutionHeaderResponse + + transactionsBytes := [][]byte{} + for _, transaction := range e.Transactions { + hexString, err := util.HexStringToByteArray(transaction) + if err != nil { + return payloadHeader, err + } + transactionsBytes = append(transactionsBytes, hexString) + } + transactionsContainer := state.TransactionsRootContainer{} + transactionsContainer.Transactions = transactionsBytes + transactionsRoot, err := transactionsContainer.HashTreeRoot() + if err != nil { + return payloadHeader, err + } + + withdrawals := []*state.Withdrawal{} + for _, withdrawal := range e.Withdrawals { + index, err := util.ToUint64(withdrawal.Index) + if err != nil { + return payloadHeader, err + } + + validatorIndex, err := util.ToUint64(withdrawal.ValidatorIndex) + if err != nil { + return payloadHeader, err + } + + amount, err := util.ToUint64(withdrawal.Amount) + if err != nil { + return payloadHeader, err + } + + address, err := util.HexStringTo20Bytes(withdrawal.Address) + if err != nil { + return payloadHeader, err + } + + withdrawals = append(withdrawals, &state.Withdrawal{ + Index: index, + ValidatorIndex: validatorIndex, + Address: address, + Amount: amount, + }) + } + withdrawalContainer := state.WithdrawalsRootContainerMainnet{} + withdrawalContainer.Withdrawals = withdrawals + withdrawalRoot, err := withdrawalContainer.HashTreeRoot() + if err != nil { + return payloadHeader, err + } + + return ExecutionHeaderResponse{ + ParentHash: e.ParentHash, + FeeRecipient: e.FeeRecipient, + StateRoot: e.StateRoot, + ReceiptsRoot: e.ReceiptsRoot, + LogsBloom: e.LogsBloom, + PrevRandao: e.PrevRandao, + BlockNumber: e.BlockNumber, + GasLimit: e.GasLimit, + GasUsed: e.GasUsed, + Timestamp: e.Timestamp, + ExtraData: e.ExtraData, + BaseFeePerGas: e.BaseFeePerGas, + BlockHash: e.BlockHash, + TransactionsRoot: common.BytesToHash(transactionsRoot[:]).String(), + WithdrawalsRoot: common.BytesToHash(withdrawalRoot[:]).String(), + BlobGasUsed: e.BlobGasUsed, + ExcessBlobGas: e.ExcessBlobGas, + }, nil +} diff --git a/relayer/relays/beacon/header/syncer/json/beacon_json.go b/relayer/relays/beacon/header/syncer/json/beacon_json.go index cda9afae1a..f8cef2fdc8 100644 --- a/relayer/relays/beacon/header/syncer/json/beacon_json.go +++ b/relayer/relays/beacon/header/syncer/json/beacon_json.go @@ -32,16 +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"` - ExecutionHeader *VersionedExecutionPayloadHeader `json:"execution_header"` - ExecutionBranch *[]string `json:"execution_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 { @@ -266,7 +266,8 @@ func (s *Update) RemoveLeadingZeroHashes() { s.FinalityBranch = removeLeadingZeroHashForSlice(s.FinalityBranch) s.BlockRootsRoot = removeLeadingZeroHash(s.BlockRootsRoot) s.BlockRootsBranch = removeLeadingZeroHashForSlice(s.BlockRootsBranch) - + s.ExecutionHeader.RemoveLeadingZeroHashes() + s.ExecutionBranch = removeLeadingZeroHashForSlice(s.ExecutionBranch) } func (h *HeaderUpdate) RemoveLeadingZeroHashes() { diff --git a/relayer/relays/beacon/header/syncer/scale/beacon_scale.go b/relayer/relays/beacon/header/syncer/scale/beacon_scale.go index a78a7f017a..0beda0255f 100644 --- a/relayer/relays/beacon/header/syncer/scale/beacon_scale.go +++ b/relayer/relays/beacon/header/syncer/scale/beacon_scale.go @@ -41,6 +41,8 @@ type UpdatePayload struct { FinalityBranch []types.H256 BlockRootsRoot types.H256 BlockRootsBranch []types.H256 + ExecutionHeader VersionedExecutionPayloadHeader + ExecutionBranch []types.H256 } type OptionNextSyncCommitteeUpdatePayload struct { diff --git a/relayer/relays/beacon/header/syncer/scale/json_conversion.go b/relayer/relays/beacon/header/syncer/scale/json_conversion.go index 69b0baa4a1..46c939ed10 100644 --- a/relayer/relays/beacon/header/syncer/scale/json_conversion.go +++ b/relayer/relays/beacon/header/syncer/scale/json_conversion.go @@ -34,6 +34,8 @@ func (p UpdatePayload) ToJSON() json.Update { FinalityBranch: util.ScaleBranchToString(p.FinalityBranch), BlockRootsRoot: p.BlockRootsRoot.Hex(), BlockRootsBranch: util.ScaleBranchToString(p.BlockRootsBranch), + ExecutionHeader: p.ExecutionHeader.ToJSON(), + ExecutionBranch: util.ScaleBranchToString(p.ExecutionBranch), } } diff --git a/relayer/relays/beacon/header/syncer/syncer.go b/relayer/relays/beacon/header/syncer/syncer.go index 2254b0ec46..e2f2062b97 100644 --- a/relayer/relays/beacon/header/syncer/syncer.go +++ b/relayer/relays/beacon/header/syncer/syncer.go @@ -255,6 +255,11 @@ func (s *Syncer) GetSyncCommitteePeriodUpdateFromEndpoint(from uint64) (scale.Up return scale.Update{}, fmt.Errorf("beacon header hash tree root: %w", err) } + versionedExecutionPayloadHeader, err := s.getVersionedScaleExecutionHeader(uint64(finalizedHeader.Slot), committeeUpdate.FinalizedHeader.ExecutionHeader) + if err != nil { + return scale.Update{}, fmt.Errorf("get versioned scale execution header: %w", err) + } + syncCommitteePeriodUpdate := scale.Update{ Payload: scale.UpdatePayload{ AttestedHeader: attestedHeader, @@ -271,6 +276,8 @@ func (s *Syncer) GetSyncCommitteePeriodUpdateFromEndpoint(from uint64) (scale.Up FinalityBranch: util.ProofBranchToScale(committeeUpdate.FinalityBranch), BlockRootsRoot: blockRootsProof.Leaf, BlockRootsBranch: blockRootsProof.Proof, + ExecutionHeader: versionedExecutionPayloadHeader, + ExecutionBranch: util.ProofBranchToScale(committeeUpdate.FinalizedHeader.ExecutionBranch), }, FinalizedHeaderBlockRoot: finalizedHeaderBlockRoot, BlockRootsTree: blockRootsProof.Tree, @@ -412,6 +419,11 @@ func (s *Syncer) GetFinalizedUpdate() (scale.Update, error) { return scale.Update{}, fmt.Errorf("parse signature slot as int: %w", err) } + versionedExecutionPayloadHeader, err := s.getVersionedScaleExecutionHeader(uint64(finalizedHeader.Slot), finalizedUpdate.Data.FinalizedHeader.ExecutionHeader) + if err != nil { + return scale.Update{}, fmt.Errorf("get versioned scale execution header: %w", err) + } + updatePayload := scale.UpdatePayload{ AttestedHeader: attestedHeader, SyncAggregate: syncAggregate, @@ -423,6 +435,8 @@ func (s *Syncer) GetFinalizedUpdate() (scale.Update, error) { FinalityBranch: util.ProofBranchToScale(finalizedUpdate.Data.FinalityBranch), BlockRootsRoot: blockRootsProof.Leaf, BlockRootsBranch: blockRootsProof.Proof, + ExecutionHeader: versionedExecutionPayloadHeader, + ExecutionBranch: util.ProofBranchToScale(finalizedUpdate.Data.FinalizedHeader.ExecutionBranch), } return scale.Update{ @@ -755,6 +769,17 @@ func (s *Syncer) GetFinalizedUpdateAtAttestedSlot(minSlot, maxSlot uint64, fetch return update, fmt.Errorf("get finalized header proof: %w", err) } + // Execution header proof + finalizedStateTree, err := data.FinalizedState.GetTree() + if err != nil { + return update, fmt.Errorf("get finalized state tree: %w", err) + } + _ = finalizedStateTree.Hash() // necessary to populate the proof tree values + executionHeaderProof, err := stateTree.Prove(ExecutionPayloadGeneralizedIndex) + if err != nil { + return update, fmt.Errorf("get execution header proof: %w", err) + } + var nextSyncCommitteeScale scale.OptionNextSyncCommitteeUpdatePayload if fetchNextSyncCommittee { nextSyncCommitteeProof, err := stateTree.Prove(NextSyncCommitteeGeneralizedIndex) @@ -825,6 +850,21 @@ func (s *Syncer) GetFinalizedUpdateAtAttestedSlot(minSlot, maxSlot uint64, fetch return update, fmt.Errorf("convert sync aggregate to scale: %w", err) } + finalizedBlock, err := s.Client.GetBeaconBlockBySlot(data.FinalizedHeader.Slot) + if err != nil { + return update, fmt.Errorf("get finalized beacon block: %w", err) + } + + executionHeaderResponse, err := finalizedBlock.Data.Message.Body.ExecutionPayload.ToExecutionHeaderResponse() + if err != nil { + return update, fmt.Errorf("get execution header response: %w", err) + } + + versionedExecutionPayloadHeader, err := s.getVersionedScaleExecutionHeader(data.FinalizedHeader.Slot, executionHeaderResponse) + if err != nil { + return scale.Update{}, fmt.Errorf("get versioned scale execution header: %w", err) + } + payload := scale.UpdatePayload{ AttestedHeader: scaleHeader, SyncAggregate: scaleSyncAggregate, @@ -834,6 +874,8 @@ func (s *Syncer) GetFinalizedUpdateAtAttestedSlot(minSlot, maxSlot uint64, fetch FinalityBranch: util.BytesBranchToScale(finalizedHeaderProof.Hashes), BlockRootsRoot: blockRootsProof.Leaf, BlockRootsBranch: blockRootsProof.Proof, + ExecutionHeader: versionedExecutionPayloadHeader, + ExecutionBranch: util.BytesBranchToScale(executionHeaderProof.Hashes), } return scale.Update{ @@ -938,6 +980,22 @@ func (s *Syncer) getBestMatchBeaconDataFromStore(minSlot, maxSlot uint64) (final return response, nil } +func (s *Syncer) getVersionedScaleExecutionHeader(slot uint64, executionHeader api.ExecutionHeaderResponse) (scale.VersionedExecutionPayloadHeader, error) { + if s.protocol.DenebForked(slot) { + executionPayloadScale, err := api.DenebJsonExecutionPayloadHeaderToScale(executionHeader) + if err != nil { + return scale.VersionedExecutionPayloadHeader{}, err + } + return scale.VersionedExecutionPayloadHeader{Deneb: &executionPayloadScale}, nil + } else { + executionPayloadScale, err := api.CapellaJsonExecutionPayloadHeaderToScale(executionHeader) + if err != nil { + return scale.VersionedExecutionPayloadHeader{}, err + } + return scale.VersionedExecutionPayloadHeader{Capella: &executionPayloadScale}, nil + } +} + func (s *Syncer) getBeaconState(slot uint64) ([]byte, error) { data, err := s.Client.GetBeaconState(strconv.FormatUint(slot, 10)) if err != nil {