diff --git a/beacon-chain/rpc/eth/light-client/handlers.go b/beacon-chain/rpc/eth/light-client/handlers.go index 84db74dc067..a4506a2de18 100644 --- a/beacon-chain/rpc/eth/light-client/handlers.go +++ b/beacon-chain/rpc/eth/light-client/handlers.go @@ -188,12 +188,25 @@ func (s *Server) GetLightClientFinalityUpdate(w http.ResponseWriter, req *http.R return } - response := &structs.LightClientFinalityUpdateResponse{ - Version: version.String(attestedState.Version()), - Data: update, + if httputil.RespondWithSsz(req) { + ssz, err := update.MarshalSSZ() + if err != nil { + httputil.HandleError(w, "Could not marshal finality update to SSZ: "+err.Error(), http.StatusInternalServerError) + return + } + httputil.WriteSsz(w, ssz, "light_client_finality_update.ssz") + } else { + updateStruct, err := structs.LightClientFinalityUpdateFromConsensus(update) + if err != nil { + httputil.HandleError(w, "Could not convert light client finality update to API struct: "+err.Error(), http.StatusInternalServerError) + return + } + response := &structs.LightClientFinalityUpdateResponse{ + Version: version.String(attestedState.Version()), + Data: updateStruct, + } + httputil.WriteJson(w, response) } - - httputil.WriteJson(w, response) } // GetLightClientOptimisticUpdate - implements https://github.com/ethereum/beacon-APIs/blob/263f4ed6c263c967f13279c7a9f5629b51c5fc55/apis/beacon/light_client/optimistic_update.yaml @@ -238,12 +251,25 @@ func (s *Server) GetLightClientOptimisticUpdate(w http.ResponseWriter, req *http return } - response := &structs.LightClientOptimisticUpdateResponse{ - Version: version.String(attestedState.Version()), - Data: update, + if httputil.RespondWithSsz(req) { + ssz, err := update.MarshalSSZ() + if err != nil { + httputil.HandleError(w, "Could not marshal optimistic update to SSZ: "+err.Error(), http.StatusInternalServerError) + return + } + httputil.WriteSsz(w, ssz, "light_client_optimistic_update.ssz") + } else { + updateStruct, err := structs.LightClientOptimisticUpdateFromConsensus(update) + if err != nil { + httputil.HandleError(w, "Could not convert light client optimistic update to API struct: "+err.Error(), http.StatusInternalServerError) + return + } + response := &structs.LightClientOptimisticUpdateResponse{ + Version: version.String(attestedState.Version()), + Data: updateStruct, + } + httputil.WriteJson(w, response) } - - httputil.WriteJson(w, response) } // suitableBlock returns the latest block that satisfies all criteria required for creating a new update diff --git a/beacon-chain/rpc/eth/light-client/handlers_test.go b/beacon-chain/rpc/eth/light-client/handlers_test.go index 422dfcba9d4..6e744e53162 100644 --- a/beacon-chain/rpc/eth/light-client/handlers_test.go +++ b/beacon-chain/rpc/eth/light-client/handlers_test.go @@ -1105,116 +1105,226 @@ func TestLightClientHandler_GetLightClientFinalityUpdate(t *testing.T) { EnableLightClient: true, }) defer resetFn() - helpers.ClearCache() - ctx := context.Background() config := params.BeaconConfig() - slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - attestedState, err := util.NewBeaconStateAltair() - require.NoError(t, err) - err = attestedState.SetSlot(slot.Sub(1)) - require.NoError(t, err) + t.Run("altair", func(t *testing.T) { + ctx := context.Background() + slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - require.NoError(t, attestedState.SetFinalizedCheckpoint(&pb.Checkpoint{ - Epoch: config.AltairForkEpoch - 10, - Root: make([]byte, 32), - })) + attestedState, err := util.NewBeaconStateAltair() + require.NoError(t, err) + err = attestedState.SetSlot(slot.Sub(1)) + require.NoError(t, err) - parent := util.NewBeaconBlockAltair() - parent.Block.Slot = slot.Sub(1) + require.NoError(t, attestedState.SetFinalizedCheckpoint(&pb.Checkpoint{ + Epoch: config.AltairForkEpoch - 10, + Root: make([]byte, 32), + })) - signedParent, err := blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) + parent := util.NewBeaconBlockAltair() + parent.Block.Slot = slot.Sub(1) - parentHeader, err := signedParent.Header() - require.NoError(t, err) - attestedHeader := parentHeader.Header + signedParent, err := blocks.NewSignedBeaconBlock(parent) + require.NoError(t, err) - err = attestedState.SetLatestBlockHeader(attestedHeader) - require.NoError(t, err) - attestedStateRoot, err := attestedState.HashTreeRoot(ctx) - require.NoError(t, err) + parentHeader, err := signedParent.Header() + require.NoError(t, err) + attestedHeader := parentHeader.Header - // get a new signed block so the root is updated with the new state root - parent.Block.StateRoot = attestedStateRoot[:] - signedParent, err = blocks.NewSignedBeaconBlock(parent) - require.NoError(t, err) + err = attestedState.SetLatestBlockHeader(attestedHeader) + require.NoError(t, err) + attestedStateRoot, err := attestedState.HashTreeRoot(ctx) + require.NoError(t, err) - st, err := util.NewBeaconStateAltair() - require.NoError(t, err) - err = st.SetSlot(slot) - require.NoError(t, err) + // get a new signed block so the root is updated with the new state root + parent.Block.StateRoot = attestedStateRoot[:] + signedParent, err = blocks.NewSignedBeaconBlock(parent) + require.NoError(t, err) - parentRoot, err := signedParent.Block().HashTreeRoot() - require.NoError(t, err) + st, err := util.NewBeaconStateAltair() + require.NoError(t, err) + err = st.SetSlot(slot) + require.NoError(t, err) - block := util.NewBeaconBlockAltair() - block.Block.Slot = slot - block.Block.ParentRoot = parentRoot[:] + parentRoot, err := signedParent.Block().HashTreeRoot() + require.NoError(t, err) - for i := uint64(0); i < config.SyncCommitteeSize; i++ { - block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) - } + block := util.NewBeaconBlockAltair() + block.Block.Slot = slot + block.Block.ParentRoot = parentRoot[:] - signedBlock, err := blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) + for i := uint64(0); i < config.SyncCommitteeSize; i++ { + block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) + } - h, err := signedBlock.Header() - require.NoError(t, err) + signedBlock, err := blocks.NewSignedBeaconBlock(block) + require.NoError(t, err) - err = st.SetLatestBlockHeader(h.Header) - require.NoError(t, err) - stateRoot, err := st.HashTreeRoot(ctx) - require.NoError(t, err) + h, err := signedBlock.Header() + require.NoError(t, err) - // get a new signed block so the root is updated with the new state root - block.Block.StateRoot = stateRoot[:] - signedBlock, err = blocks.NewSignedBeaconBlock(block) - require.NoError(t, err) + err = st.SetLatestBlockHeader(h.Header) + require.NoError(t, err) + stateRoot, err := st.HashTreeRoot(ctx) + require.NoError(t, err) - root, err := block.Block.HashTreeRoot() - require.NoError(t, err) + // get a new signed block so the root is updated with the new state root + block.Block.StateRoot = stateRoot[:] + signedBlock, err = blocks.NewSignedBeaconBlock(block) + require.NoError(t, err) - mockBlocker := &testutil.MockBlocker{ - RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ - parentRoot: signedParent, - root: signedBlock, - }, - SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ - slot.Sub(1): signedParent, - slot: signedBlock, - }, - } - mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st, FinalizedRoots: map[[32]byte]bool{ - root: true, - }} - mockChainInfoFetcher := &mock.ChainService{Slot: &slot} - s := &Server{ - Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ - slot.Sub(1): attestedState, - slot: st, - }}, - Blocker: mockBlocker, - HeadFetcher: mockChainService, - ChainInfoFetcher: mockChainInfoFetcher, - } - request := httptest.NewRequest("GET", "http://foo.com", nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + root, err := block.Block.HashTreeRoot() + require.NoError(t, err) + + mockBlocker := &testutil.MockBlocker{ + RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ + parentRoot: signedParent, + root: signedBlock, + }, + SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ + slot.Sub(1): signedParent, + slot: signedBlock, + }, + } + mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st, FinalizedRoots: map[[32]byte]bool{ + root: true, + }} + mockChainInfoFetcher := &mock.ChainService{Slot: &slot} + s := &Server{ + Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ + slot.Sub(1): attestedState, + slot: st, + }}, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + ChainInfoFetcher: mockChainInfoFetcher, + } + request := httptest.NewRequest("GET", "http://foo.com", nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - s.GetLightClientFinalityUpdate(writer, request) + s.GetLightClientFinalityUpdate(writer, request) - require.Equal(t, http.StatusOK, writer.Code) - var resp *structs.LightClientUpdateResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp) - require.NoError(t, err) - var respHeader structs.LightClientHeader - err = json.Unmarshal(resp.Data.AttestedHeader, &respHeader) - require.NoError(t, err) - require.Equal(t, "altair", resp.Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp.Data) + require.Equal(t, http.StatusOK, writer.Code) + var resp *structs.LightClientUpdateResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp) + require.NoError(t, err) + var respHeader structs.LightClientHeader + err = json.Unmarshal(resp.Data.AttestedHeader, &respHeader) + require.NoError(t, err) + require.Equal(t, "altair", resp.Version) + require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) + require.NotNil(t, resp.Data) + }) + + t.Run("altair SSZ", func(t *testing.T) { + ctx := context.Background() + slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + + attestedState, err := util.NewBeaconStateAltair() + require.NoError(t, err) + err = attestedState.SetSlot(slot.Sub(1)) + require.NoError(t, err) + + require.NoError(t, attestedState.SetFinalizedCheckpoint(&pb.Checkpoint{ + Epoch: config.AltairForkEpoch - 10, + Root: make([]byte, 32), + })) + + parent := util.NewBeaconBlockAltair() + parent.Block.Slot = slot.Sub(1) + + signedParent, err := blocks.NewSignedBeaconBlock(parent) + require.NoError(t, err) + + parentHeader, err := signedParent.Header() + require.NoError(t, err) + attestedHeader := parentHeader.Header + + err = attestedState.SetLatestBlockHeader(attestedHeader) + require.NoError(t, err) + attestedStateRoot, err := attestedState.HashTreeRoot(ctx) + require.NoError(t, err) + + // get a new signed block so the root is updated with the new state root + parent.Block.StateRoot = attestedStateRoot[:] + signedParent, err = blocks.NewSignedBeaconBlock(parent) + require.NoError(t, err) + + st, err := util.NewBeaconStateAltair() + require.NoError(t, err) + err = st.SetSlot(slot) + require.NoError(t, err) + + parentRoot, err := signedParent.Block().HashTreeRoot() + require.NoError(t, err) + + block := util.NewBeaconBlockAltair() + block.Block.Slot = slot + block.Block.ParentRoot = parentRoot[:] + + for i := uint64(0); i < config.SyncCommitteeSize; i++ { + block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) + } + + signedBlock, err := blocks.NewSignedBeaconBlock(block) + require.NoError(t, err) + + h, err := signedBlock.Header() + require.NoError(t, err) + + err = st.SetLatestBlockHeader(h.Header) + require.NoError(t, err) + stateRoot, err := st.HashTreeRoot(ctx) + require.NoError(t, err) + + // get a new signed block so the root is updated with the new state root + block.Block.StateRoot = stateRoot[:] + signedBlock, err = blocks.NewSignedBeaconBlock(block) + require.NoError(t, err) + + root, err := block.Block.HashTreeRoot() + require.NoError(t, err) + + mockBlocker := &testutil.MockBlocker{ + RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ + parentRoot: signedParent, + root: signedBlock, + }, + SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ + slot.Sub(1): signedParent, + slot: signedBlock, + }, + } + mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st, FinalizedRoots: map[[32]byte]bool{ + root: true, + }} + mockChainInfoFetcher := &mock.ChainService{Slot: &slot} + s := &Server{ + Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ + slot.Sub(1): attestedState, + slot: st, + }}, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + ChainInfoFetcher: mockChainInfoFetcher, + } + request := httptest.NewRequest("GET", "http://foo.com", nil) + request.Header.Add("Accept", "application/octet-stream") + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetLightClientFinalityUpdate(writer, request) + + require.Equal(t, http.StatusOK, writer.Code) + + var resp pb.LightClientFinalityUpdateAltair + err = resp.UnmarshalSSZ(writer.Body.Bytes()) + require.NoError(t, err) + require.Equal(t, attestedHeader.Slot, resp.AttestedHeader.Beacon.Slot) + require.DeepEqual(t, attestedHeader.BodyRoot, resp.AttestedHeader.Beacon.BodyRoot) + }) } func TestLightClientHandler_GetLightClientOptimisticUpdate(t *testing.T) { @@ -1335,11 +1445,11 @@ func TestLightClientHandler_GetLightClientOptimisticUpdate(t *testing.T) { require.NotNil(t, resp.Data) }) - t.Run("capella", func(t *testing.T) { + t.Run("altair SSZ", func(t *testing.T) { ctx := context.Background() - slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - attestedState, err := util.NewBeaconStateCapella() + attestedState, err := util.NewBeaconStateAltair() require.NoError(t, err) err = attestedState.SetSlot(slot.Sub(1)) require.NoError(t, err) @@ -1349,7 +1459,7 @@ func TestLightClientHandler_GetLightClientOptimisticUpdate(t *testing.T) { Root: make([]byte, 32), })) - parent := util.NewBeaconBlockCapella() + parent := util.NewBeaconBlockAltair() parent.Block.Slot = slot.Sub(1) signedParent, err := blocks.NewSignedBeaconBlock(parent) @@ -1369,7 +1479,7 @@ func TestLightClientHandler_GetLightClientOptimisticUpdate(t *testing.T) { signedParent, err = blocks.NewSignedBeaconBlock(parent) require.NoError(t, err) - st, err := util.NewBeaconStateCapella() + st, err := util.NewBeaconStateAltair() require.NoError(t, err) err = st.SetSlot(slot) require.NoError(t, err) @@ -1377,7 +1487,7 @@ func TestLightClientHandler_GetLightClientOptimisticUpdate(t *testing.T) { parentRoot, err := signedParent.Block().HashTreeRoot() require.NoError(t, err) - block := util.NewBeaconBlockCapella() + block := util.NewBeaconBlockAltair() block.Block.Slot = slot block.Block.ParentRoot = parentRoot[:] @@ -1428,28 +1538,26 @@ func TestLightClientHandler_GetLightClientOptimisticUpdate(t *testing.T) { ChainInfoFetcher: mockChainInfoFetcher, } request := httptest.NewRequest("GET", "http://foo.com", nil) + request.Header.Add("Accept", "application/octet-stream") writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} s.GetLightClientOptimisticUpdate(writer, request) require.Equal(t, http.StatusOK, writer.Code) - var resp *structs.LightClientUpdateResponse - err = json.Unmarshal(writer.Body.Bytes(), &resp) - require.NoError(t, err) - var respHeader structs.LightClientHeaderCapella - err = json.Unmarshal(resp.Data.AttestedHeader, &respHeader) + + var resp pb.LightClientOptimisticUpdateAltair + err = resp.UnmarshalSSZ(writer.Body.Bytes()) require.NoError(t, err) - require.Equal(t, "capella", resp.Version) - require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) - require.NotNil(t, resp.Data) + require.Equal(t, resp.AttestedHeader.Beacon.Slot, attestedHeader.Slot) + require.DeepEqual(t, resp.AttestedHeader.Beacon.BodyRoot, attestedHeader.BodyRoot) }) - t.Run("deneb", func(t *testing.T) { + t.Run("capella", func(t *testing.T) { ctx := context.Background() - slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) - attestedState, err := util.NewBeaconStateDeneb() + attestedState, err := util.NewBeaconStateCapella() require.NoError(t, err) err = attestedState.SetSlot(slot.Sub(1)) require.NoError(t, err) @@ -1459,7 +1567,7 @@ func TestLightClientHandler_GetLightClientOptimisticUpdate(t *testing.T) { Root: make([]byte, 32), })) - parent := util.NewBeaconBlockDeneb() + parent := util.NewBeaconBlockCapella() parent.Block.Slot = slot.Sub(1) signedParent, err := blocks.NewSignedBeaconBlock(parent) @@ -1479,7 +1587,7 @@ func TestLightClientHandler_GetLightClientOptimisticUpdate(t *testing.T) { signedParent, err = blocks.NewSignedBeaconBlock(parent) require.NoError(t, err) - st, err := util.NewBeaconStateDeneb() + st, err := util.NewBeaconStateCapella() require.NoError(t, err) err = st.SetSlot(slot) require.NoError(t, err) @@ -1487,7 +1595,7 @@ func TestLightClientHandler_GetLightClientOptimisticUpdate(t *testing.T) { parentRoot, err := signedParent.Block().HashTreeRoot() require.NoError(t, err) - block := util.NewBeaconBlockDeneb() + block := util.NewBeaconBlockCapella() block.Block.Slot = slot block.Block.ParentRoot = parentRoot[:] @@ -1547,13 +1655,339 @@ func TestLightClientHandler_GetLightClientOptimisticUpdate(t *testing.T) { var resp *structs.LightClientUpdateResponse err = json.Unmarshal(writer.Body.Bytes(), &resp) require.NoError(t, err) - var respHeader structs.LightClientHeaderDeneb + var respHeader structs.LightClientHeaderCapella err = json.Unmarshal(resp.Data.AttestedHeader, &respHeader) require.NoError(t, err) - require.Equal(t, "deneb", resp.Version) + require.Equal(t, "capella", resp.Version) require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) require.NotNil(t, resp.Data) }) + + t.Run("capella SSZ", func(t *testing.T) { + ctx := context.Background() + slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + + attestedState, err := util.NewBeaconStateCapella() + require.NoError(t, err) + err = attestedState.SetSlot(slot.Sub(1)) + require.NoError(t, err) + + require.NoError(t, attestedState.SetFinalizedCheckpoint(&pb.Checkpoint{ + Epoch: config.AltairForkEpoch - 10, + Root: make([]byte, 32), + })) + + parent := util.NewBeaconBlockCapella() + parent.Block.Slot = slot.Sub(1) + + signedParent, err := blocks.NewSignedBeaconBlock(parent) + require.NoError(t, err) + + parentHeader, err := signedParent.Header() + require.NoError(t, err) + attestedHeader := parentHeader.Header + + err = attestedState.SetLatestBlockHeader(attestedHeader) + require.NoError(t, err) + attestedStateRoot, err := attestedState.HashTreeRoot(ctx) + require.NoError(t, err) + + // get a new signed block so the root is updated with the new state root + parent.Block.StateRoot = attestedStateRoot[:] + signedParent, err = blocks.NewSignedBeaconBlock(parent) + require.NoError(t, err) + + st, err := util.NewBeaconStateCapella() + require.NoError(t, err) + err = st.SetSlot(slot) + require.NoError(t, err) + + parentRoot, err := signedParent.Block().HashTreeRoot() + require.NoError(t, err) + + block := util.NewBeaconBlockCapella() + block.Block.Slot = slot + block.Block.ParentRoot = parentRoot[:] + + for i := uint64(0); i < config.SyncCommitteeSize; i++ { + block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) + } + + signedBlock, err := blocks.NewSignedBeaconBlock(block) + require.NoError(t, err) + + h, err := signedBlock.Header() + require.NoError(t, err) + + err = st.SetLatestBlockHeader(h.Header) + require.NoError(t, err) + stateRoot, err := st.HashTreeRoot(ctx) + require.NoError(t, err) + + // get a new signed block so the root is updated with the new state root + block.Block.StateRoot = stateRoot[:] + signedBlock, err = blocks.NewSignedBeaconBlock(block) + require.NoError(t, err) + + root, err := block.Block.HashTreeRoot() + require.NoError(t, err) + + mockBlocker := &testutil.MockBlocker{ + RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ + parentRoot: signedParent, + root: signedBlock, + }, + SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ + slot.Sub(1): signedParent, + slot: signedBlock, + }, + } + mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st, FinalizedRoots: map[[32]byte]bool{ + root: true, + }} + mockChainInfoFetcher := &mock.ChainService{Slot: &slot} + s := &Server{ + Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ + slot.Sub(1): attestedState, + slot: st, + }}, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + ChainInfoFetcher: mockChainInfoFetcher, + } + request := httptest.NewRequest("GET", "http://foo.com", nil) + request.Header.Add("Accept", "application/octet-stream") + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetLightClientOptimisticUpdate(writer, request) + + require.Equal(t, http.StatusOK, writer.Code) + + var resp pb.LightClientOptimisticUpdateCapella + err = resp.UnmarshalSSZ(writer.Body.Bytes()) + require.NoError(t, err) + require.Equal(t, resp.AttestedHeader.Beacon.Slot, attestedHeader.Slot) + require.DeepEqual(t, resp.AttestedHeader.Beacon.BodyRoot, attestedHeader.BodyRoot) + }) + + t.Run("deneb", func(t *testing.T) { + ctx := context.Background() + slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + + attestedState, err := util.NewBeaconStateDeneb() + require.NoError(t, err) + err = attestedState.SetSlot(slot.Sub(1)) + require.NoError(t, err) + + require.NoError(t, attestedState.SetFinalizedCheckpoint(&pb.Checkpoint{ + Epoch: config.AltairForkEpoch - 10, + Root: make([]byte, 32), + })) + + parent := util.NewBeaconBlockDeneb() + parent.Block.Slot = slot.Sub(1) + + signedParent, err := blocks.NewSignedBeaconBlock(parent) + require.NoError(t, err) + + parentHeader, err := signedParent.Header() + require.NoError(t, err) + attestedHeader := parentHeader.Header + + err = attestedState.SetLatestBlockHeader(attestedHeader) + require.NoError(t, err) + attestedStateRoot, err := attestedState.HashTreeRoot(ctx) + require.NoError(t, err) + + // get a new signed block so the root is updated with the new state root + parent.Block.StateRoot = attestedStateRoot[:] + signedParent, err = blocks.NewSignedBeaconBlock(parent) + require.NoError(t, err) + + st, err := util.NewBeaconStateDeneb() + require.NoError(t, err) + err = st.SetSlot(slot) + require.NoError(t, err) + + parentRoot, err := signedParent.Block().HashTreeRoot() + require.NoError(t, err) + + block := util.NewBeaconBlockDeneb() + block.Block.Slot = slot + block.Block.ParentRoot = parentRoot[:] + + for i := uint64(0); i < config.SyncCommitteeSize; i++ { + block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) + } + + signedBlock, err := blocks.NewSignedBeaconBlock(block) + require.NoError(t, err) + + h, err := signedBlock.Header() + require.NoError(t, err) + + err = st.SetLatestBlockHeader(h.Header) + require.NoError(t, err) + stateRoot, err := st.HashTreeRoot(ctx) + require.NoError(t, err) + + // get a new signed block so the root is updated with the new state root + block.Block.StateRoot = stateRoot[:] + signedBlock, err = blocks.NewSignedBeaconBlock(block) + require.NoError(t, err) + + root, err := block.Block.HashTreeRoot() + require.NoError(t, err) + + mockBlocker := &testutil.MockBlocker{ + RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ + parentRoot: signedParent, + root: signedBlock, + }, + SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ + slot.Sub(1): signedParent, + slot: signedBlock, + }, + } + mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st, FinalizedRoots: map[[32]byte]bool{ + root: true, + }} + mockChainInfoFetcher := &mock.ChainService{Slot: &slot} + s := &Server{ + Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ + slot.Sub(1): attestedState, + slot: st, + }}, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + ChainInfoFetcher: mockChainInfoFetcher, + } + request := httptest.NewRequest("GET", "http://foo.com", nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetLightClientOptimisticUpdate(writer, request) + + require.Equal(t, http.StatusOK, writer.Code) + var resp *structs.LightClientUpdateResponse + err = json.Unmarshal(writer.Body.Bytes(), &resp) + require.NoError(t, err) + var respHeader structs.LightClientHeaderDeneb + err = json.Unmarshal(resp.Data.AttestedHeader, &respHeader) + require.NoError(t, err) + require.Equal(t, "deneb", resp.Version) + require.Equal(t, hexutil.Encode(attestedHeader.BodyRoot), respHeader.Beacon.BodyRoot) + require.NotNil(t, resp.Data) + }) + + t.Run("deneb SSZ", func(t *testing.T) { + ctx := context.Background() + slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1) + + attestedState, err := util.NewBeaconStateDeneb() + require.NoError(t, err) + err = attestedState.SetSlot(slot.Sub(1)) + require.NoError(t, err) + + require.NoError(t, attestedState.SetFinalizedCheckpoint(&pb.Checkpoint{ + Epoch: config.AltairForkEpoch - 10, + Root: make([]byte, 32), + })) + + parent := util.NewBeaconBlockDeneb() + parent.Block.Slot = slot.Sub(1) + + signedParent, err := blocks.NewSignedBeaconBlock(parent) + require.NoError(t, err) + + parentHeader, err := signedParent.Header() + require.NoError(t, err) + attestedHeader := parentHeader.Header + + err = attestedState.SetLatestBlockHeader(attestedHeader) + require.NoError(t, err) + attestedStateRoot, err := attestedState.HashTreeRoot(ctx) + require.NoError(t, err) + + // get a new signed block so the root is updated with the new state root + parent.Block.StateRoot = attestedStateRoot[:] + signedParent, err = blocks.NewSignedBeaconBlock(parent) + require.NoError(t, err) + + st, err := util.NewBeaconStateDeneb() + require.NoError(t, err) + err = st.SetSlot(slot) + require.NoError(t, err) + + parentRoot, err := signedParent.Block().HashTreeRoot() + require.NoError(t, err) + + block := util.NewBeaconBlockDeneb() + block.Block.Slot = slot + block.Block.ParentRoot = parentRoot[:] + + for i := uint64(0); i < config.SyncCommitteeSize; i++ { + block.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true) + } + + signedBlock, err := blocks.NewSignedBeaconBlock(block) + require.NoError(t, err) + + h, err := signedBlock.Header() + require.NoError(t, err) + + err = st.SetLatestBlockHeader(h.Header) + require.NoError(t, err) + stateRoot, err := st.HashTreeRoot(ctx) + require.NoError(t, err) + + // get a new signed block so the root is updated with the new state root + block.Block.StateRoot = stateRoot[:] + signedBlock, err = blocks.NewSignedBeaconBlock(block) + require.NoError(t, err) + + root, err := block.Block.HashTreeRoot() + require.NoError(t, err) + + mockBlocker := &testutil.MockBlocker{ + RootBlockMap: map[[32]byte]interfaces.ReadOnlySignedBeaconBlock{ + parentRoot: signedParent, + root: signedBlock, + }, + SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{ + slot.Sub(1): signedParent, + slot: signedBlock, + }, + } + mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot, State: st, FinalizedRoots: map[[32]byte]bool{ + root: true, + }} + mockChainInfoFetcher := &mock.ChainService{Slot: &slot} + s := &Server{ + Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{ + slot.Sub(1): attestedState, + slot: st, + }}, + Blocker: mockBlocker, + HeadFetcher: mockChainService, + ChainInfoFetcher: mockChainInfoFetcher, + } + request := httptest.NewRequest("GET", "http://foo.com", nil) + request.Header.Add("Accept", "application/octet-stream") + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetLightClientOptimisticUpdate(writer, request) + + require.Equal(t, http.StatusOK, writer.Code) + + var resp pb.LightClientOptimisticUpdateDeneb + err = resp.UnmarshalSSZ(writer.Body.Bytes()) + require.NoError(t, err) + require.Equal(t, resp.AttestedHeader.Beacon.Slot, attestedHeader.Slot) + require.DeepEqual(t, resp.AttestedHeader.Beacon.BodyRoot, attestedHeader.BodyRoot) + }) } func TestLightClientHandler_GetLightClientEventBlock(t *testing.T) { diff --git a/beacon-chain/rpc/eth/light-client/helpers.go b/beacon-chain/rpc/eth/light-client/helpers.go index 779bbd447db..d29c6cbb079 100644 --- a/beacon-chain/rpc/eth/light-client/helpers.go +++ b/beacon-chain/rpc/eth/light-client/helpers.go @@ -5,7 +5,6 @@ import ( "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" - "github.com/prysmaticlabs/prysm/v5/api/server/structs" lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" @@ -19,13 +18,8 @@ func newLightClientFinalityUpdateFromBeaconState( attestedState state.BeaconState, attestedBlock interfaces.ReadOnlySignedBeaconBlock, finalizedBlock interfaces.ReadOnlySignedBeaconBlock, -) (*structs.LightClientFinalityUpdate, error) { - result, err := lightclient.NewLightClientFinalityUpdateFromBeaconState(ctx, currentSlot, state, block, attestedState, attestedBlock, finalizedBlock) - if err != nil { - return nil, err - } - - return structs.LightClientFinalityUpdateFromConsensus(result) +) (interfaces.LightClientFinalityUpdate, error) { + return lightclient.NewLightClientFinalityUpdateFromBeaconState(ctx, currentSlot, state, block, attestedState, attestedBlock, finalizedBlock) } func newLightClientOptimisticUpdateFromBeaconState( @@ -35,11 +29,6 @@ func newLightClientOptimisticUpdateFromBeaconState( block interfaces.ReadOnlySignedBeaconBlock, attestedState state.BeaconState, attestedBlock interfaces.ReadOnlySignedBeaconBlock, -) (*structs.LightClientOptimisticUpdate, error) { - result, err := lightclient.NewLightClientOptimisticUpdateFromBeaconState(ctx, currentSlot, state, block, attestedState, attestedBlock) - if err != nil { - return nil, err - } - - return structs.LightClientOptimisticUpdateFromConsensus(result) +) (interfaces.LightClientOptimisticUpdate, error) { + return lightclient.NewLightClientOptimisticUpdateFromBeaconState(ctx, currentSlot, state, block, attestedState, attestedBlock) } diff --git a/changelog/bastin_lightclient-finality-optimistic-ssz.md b/changelog/bastin_lightclient-finality-optimistic-ssz.md new file mode 100644 index 00000000000..c53abe20ecc --- /dev/null +++ b/changelog/bastin_lightclient-finality-optimistic-ssz.md @@ -0,0 +1,3 @@ +### Added + +- Add SSZ support to light client finality and optimistic APIs. [[PR]](https://github.com/prysmaticlabs/prysm/pull/14836) \ No newline at end of file