-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bab123c
commit 13da868
Showing
21 changed files
with
1,058 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
package handler | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"strconv" | ||
|
||
libcommon "github.com/ledgerwatch/erigon-lib/common" | ||
"github.com/ledgerwatch/erigon/cl/beacon/beaconhttp" | ||
state_accessors "github.com/ledgerwatch/erigon/cl/persistence/state" | ||
"github.com/ledgerwatch/erigon/cl/phase1/core/state" | ||
) | ||
|
||
type attesterDutyResponse struct { | ||
Pubkey libcommon.Bytes48 `json:"pubkey"` | ||
ValidatorIndex uint64 `json:"validator_index,string"` | ||
CommitteeIndex uint64 `json:"committee_index,string"` | ||
CommitteeLength uint64 `json:"committee_length,string"` | ||
ValidatorCommitteeIndex uint64 `json:"validator_committee_index,string"` | ||
CommitteesAtSlot uint64 `json:"committees_at_slot,string"` | ||
Slot uint64 `json:"slot,string"` | ||
} | ||
|
||
func (a *ApiHandler) getAttesterDuties(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) { | ||
epoch, err := epochFromRequest(r) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var idxsStr []string | ||
if err := json.NewDecoder(r.Body).Decode(&idxsStr); err != nil { | ||
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, fmt.Errorf("could not decode request body: %w. request body is required", err).Error()) | ||
} | ||
if len(idxsStr) == 0 { | ||
return newBeaconResponse([]string{}).withOptimistic(false), nil | ||
} | ||
idxSet := map[int]struct{}{} | ||
// convert the request to uint64 | ||
for _, idxStr := range idxsStr { | ||
|
||
idx, err := strconv.ParseUint(idxStr, 10, 64) | ||
if err != nil { | ||
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, fmt.Errorf("could not parse validator index: %w", err).Error()) | ||
} | ||
if _, ok := idxSet[int(idx)]; ok { | ||
continue | ||
} | ||
idxSet[int(idx)] = struct{}{} | ||
} | ||
|
||
tx, err := a.indiciesDB.BeginRo(r.Context()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer tx.Rollback() | ||
|
||
resp := []attesterDutyResponse{} | ||
|
||
// get the duties | ||
if a.forkchoiceStore.LowestAvaiableSlot() <= epoch*a.beaconChainCfg.SlotsPerEpoch { | ||
// non-finality case | ||
s, cn := a.syncedData.HeadState() | ||
defer cn() | ||
if s == nil { | ||
return nil, beaconhttp.NewEndpointError(http.StatusServiceUnavailable, "node is syncing") | ||
} | ||
|
||
if epoch > state.Epoch(s)+1 { | ||
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, fmt.Sprintf("epoch %d is too far in the future", epoch)) | ||
} | ||
|
||
// get active validator indicies | ||
committeeCount := s.CommitteeCount(epoch) | ||
// now start obtaining the committees from the head state | ||
for currSlot := epoch * a.beaconChainCfg.SlotsPerEpoch; currSlot < (epoch+1)*a.beaconChainCfg.SlotsPerEpoch; currSlot++ { | ||
for committeeIndex := uint64(0); committeeIndex < committeeCount; committeeIndex++ { | ||
idxs, err := s.GetBeaconCommitee(currSlot, committeeIndex) | ||
if err != nil { | ||
return nil, err | ||
} | ||
for vIdx, idx := range idxs { | ||
if _, ok := idxSet[int(idx)]; !ok { | ||
continue | ||
} | ||
publicKey, err := s.ValidatorPublicKey(int(idx)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
duty := attesterDutyResponse{ | ||
Pubkey: publicKey, | ||
ValidatorIndex: idx, | ||
CommitteeIndex: committeeIndex, | ||
CommitteeLength: uint64(len(idxs)), | ||
ValidatorCommitteeIndex: uint64(vIdx), | ||
CommitteesAtSlot: committeeCount, | ||
Slot: currSlot, | ||
} | ||
resp = append(resp, duty) | ||
} | ||
} | ||
} | ||
return newBeaconResponse(resp).withOptimistic(false), nil | ||
} | ||
|
||
stageStateProgress, err := state_accessors.GetStateProcessingProgress(tx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if (epoch)*a.beaconChainCfg.SlotsPerEpoch >= stageStateProgress { | ||
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, fmt.Sprintf("epoch %d is too far in the future", epoch)) | ||
} | ||
// finality case | ||
activeIdxs, err := state_accessors.ReadActiveIndicies(tx, epoch*a.beaconChainCfg.SlotsPerEpoch) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
committeesPerSlot := uint64(len(activeIdxs)) / a.beaconChainCfg.SlotsPerEpoch / a.beaconChainCfg.TargetCommitteeSize | ||
if a.beaconChainCfg.MaxCommitteesPerSlot < committeesPerSlot { | ||
committeesPerSlot = a.beaconChainCfg.MaxCommitteesPerSlot | ||
} | ||
if committeesPerSlot < 1 { | ||
committeesPerSlot = 1 | ||
} | ||
|
||
mixPosition := (epoch + a.beaconChainCfg.EpochsPerHistoricalVector - a.beaconChainCfg.MinSeedLookahead - 1) % a.beaconChainCfg.EpochsPerHistoricalVector | ||
mix, err := a.stateReader.ReadRandaoMixBySlotAndIndex(tx, epoch*a.beaconChainCfg.SlotsPerEpoch, mixPosition) | ||
if err != nil { | ||
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read randao mix: %v", err)) | ||
} | ||
|
||
for currSlot := epoch * a.beaconChainCfg.SlotsPerEpoch; currSlot < (epoch+1)*a.beaconChainCfg.SlotsPerEpoch; currSlot++ { | ||
for committeeIndex := uint64(0); committeeIndex < committeesPerSlot; committeeIndex++ { | ||
index := (currSlot%a.beaconChainCfg.SlotsPerEpoch)*committeesPerSlot + committeeIndex | ||
committeeCount := committeesPerSlot * a.beaconChainCfg.SlotsPerEpoch | ||
idxs, err := a.stateReader.ComputeCommittee(mix, activeIdxs, currSlot, committeeCount, index) | ||
if err != nil { | ||
return nil, err | ||
} | ||
for vIdx, idx := range idxs { | ||
if _, ok := idxSet[int(idx)]; !ok { | ||
continue | ||
} | ||
publicKey, err := state_accessors.ReadPublicKeyByIndex(tx, idx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
duty := attesterDutyResponse{ | ||
Pubkey: publicKey, | ||
ValidatorIndex: idx, | ||
CommitteeIndex: committeeIndex, | ||
CommitteeLength: uint64(len(idxs)), | ||
ValidatorCommitteeIndex: uint64(vIdx), | ||
CommitteesAtSlot: committeesPerSlot, | ||
Slot: currSlot, | ||
} | ||
resp = append(resp, duty) | ||
} | ||
} | ||
} | ||
return newBeaconResponse(resp).withOptimistic(false), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
package handler | ||
|
||
import ( | ||
"bytes" | ||
"io" | ||
"math" | ||
"net/http" | ||
"net/http/httptest" | ||
"strconv" | ||
"testing" | ||
|
||
"github.com/ledgerwatch/erigon/cl/clparams" | ||
"github.com/ledgerwatch/erigon/cl/cltypes/solid" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestDutiesAttesterAntiquated(t *testing.T) { | ||
|
||
// setupTestingHandler(t, clparams.Phase0Version) | ||
_, blocks, _, _, postState, handler, _, _, fcu := setupTestingHandler(t, clparams.Phase0Version) | ||
|
||
fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot | ||
|
||
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32) | ||
fcu.FinalizedSlotVal = math.MaxUint64 | ||
|
||
fcu.StateAtBlockRootVal[fcu.HeadVal] = postState | ||
|
||
cases := []struct { | ||
name string | ||
epoch string | ||
code int | ||
reqBody string | ||
expected string | ||
}{ | ||
{ | ||
name: "non-empty-indicies", | ||
epoch: strconv.FormatUint(fcu.HeadSlotVal/32, 10), | ||
code: http.StatusOK, | ||
reqBody: `["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]`, | ||
expected: `{"data":[{"pubkey":"0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb","validator_index":"0","committee_index":"0","committee_length":"14","validator_committee_index":"0","committees_at_slot":"1","slot":"8322"},{"pubkey":"0xb0e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc","validator_index":"4","committee_index":"0","committee_length":"13","validator_committee_index":"5","committees_at_slot":"1","slot":"8327"},{"pubkey":"0xb928f3beb93519eecf0145da903b40a4c97dca00b21f12ac0df3be9116ef2ef27b2ae6bcd4c5bc2d54ef5a70627efcb7","validator_index":"6","committee_index":"0","committee_length":"13","validator_committee_index":"10","committees_at_slot":"1","slot":"8327"},{"pubkey":"0xa6e82f6da4520f85c5d27d8f329eccfa05944fd1096b20734c894966d12a9e2a9a9744529d7212d33883113a0cadb909","validator_index":"5","committee_index":"0","committee_length":"14","validator_committee_index":"10","committees_at_slot":"1","slot":"8329"},{"pubkey":"0x89ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e5224","validator_index":"2","committee_index":"0","committee_length":"14","validator_committee_index":"11","committees_at_slot":"1","slot":"8331"},{"pubkey":"0xaf81da25ecf1c84b577fefbedd61077a81dc43b00304015b2b596ab67f00e41c86bb00ebd0f90d4b125eb0539891aeed","validator_index":"9","committee_index":"0","committee_length":"14","validator_committee_index":"8","committees_at_slot":"1","slot":"8342"},{"pubkey":"0xac9b60d5afcbd5663a8a44b7c5a02f19e9a77ab0a35bd65809bb5c67ec582c897feb04decc694b13e08587f3ff9b5b60","validator_index":"3","committee_index":"0","committee_length":"13","validator_committee_index":"6","committees_at_slot":"1","slot":"8348"}],"execution_optimistic":false}` + "\n", | ||
}, | ||
{ | ||
name: "empty-index", | ||
epoch: strconv.FormatUint(fcu.HeadSlotVal/32, 10), | ||
code: http.StatusOK, | ||
reqBody: `[]`, | ||
expected: `{"data":[],"execution_optimistic":false}` + "\n", | ||
}, | ||
{ | ||
name: "404", | ||
reqBody: `["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]`, | ||
epoch: `999999999`, | ||
code: http.StatusBadRequest, | ||
}, | ||
} | ||
|
||
for _, c := range cases { | ||
t.Run(c.name, func(t *testing.T) { | ||
server := httptest.NewServer(handler.mux) | ||
defer server.Close() | ||
// | ||
body := bytes.Buffer{} | ||
body.WriteString(c.reqBody) | ||
// Query the block in the handler with /eth/v2/beacon/states/{block_id} with content-type octet-stream | ||
req, err := http.NewRequest("POST", server.URL+"/eth/v1/validator/duties/attester/"+c.epoch, &body) | ||
require.NoError(t, err) | ||
|
||
resp, err := http.DefaultClient.Do(req) | ||
require.NoError(t, err) | ||
|
||
defer resp.Body.Close() | ||
require.Equal(t, c.code, resp.StatusCode) | ||
if resp.StatusCode != http.StatusOK { | ||
return | ||
} | ||
// read the all of the octect | ||
out, err := io.ReadAll(resp.Body) | ||
require.NoError(t, err) | ||
require.Equal(t, c.expected, string(out)) | ||
}) | ||
} | ||
} | ||
|
||
func TestDutiesAttesterNonAntiquated(t *testing.T) { | ||
|
||
// setupTestingHandler(t, clparams.Phase0Version) | ||
_, blocks, _, _, postState, handler, _, sm, fcu := setupTestingHandler(t, clparams.Phase0Version) | ||
|
||
fcu.HeadSlotVal = blocks[len(blocks)-1].Block.Slot | ||
|
||
fcu.FinalizedCheckpointVal = solid.NewCheckpointFromParameters(fcu.HeadVal, fcu.HeadSlotVal/32) | ||
fcu.FinalizedSlotVal = 0 | ||
|
||
fcu.StateAtBlockRootVal[fcu.HeadVal] = postState | ||
require.NoError(t, sm.OnHeadState(postState)) | ||
cases := []struct { | ||
name string | ||
epoch string | ||
code int | ||
reqBody string | ||
expected string | ||
}{ | ||
{ | ||
name: "non-empty-indicies", | ||
epoch: strconv.FormatUint(fcu.HeadSlotVal/32, 10), | ||
code: http.StatusOK, | ||
reqBody: `["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]`, | ||
expected: `{"data":[{"pubkey":"0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb","validator_index":"0","committee_index":"0","committee_length":"14","validator_committee_index":"0","committees_at_slot":"1","slot":"8322"},{"pubkey":"0xb0e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc","validator_index":"4","committee_index":"0","committee_length":"13","validator_committee_index":"5","committees_at_slot":"1","slot":"8327"},{"pubkey":"0xb928f3beb93519eecf0145da903b40a4c97dca00b21f12ac0df3be9116ef2ef27b2ae6bcd4c5bc2d54ef5a70627efcb7","validator_index":"6","committee_index":"0","committee_length":"13","validator_committee_index":"10","committees_at_slot":"1","slot":"8327"},{"pubkey":"0xa6e82f6da4520f85c5d27d8f329eccfa05944fd1096b20734c894966d12a9e2a9a9744529d7212d33883113a0cadb909","validator_index":"5","committee_index":"0","committee_length":"14","validator_committee_index":"10","committees_at_slot":"1","slot":"8329"},{"pubkey":"0x89ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e5224","validator_index":"2","committee_index":"0","committee_length":"14","validator_committee_index":"11","committees_at_slot":"1","slot":"8331"},{"pubkey":"0xaf81da25ecf1c84b577fefbedd61077a81dc43b00304015b2b596ab67f00e41c86bb00ebd0f90d4b125eb0539891aeed","validator_index":"9","committee_index":"0","committee_length":"14","validator_committee_index":"8","committees_at_slot":"1","slot":"8342"},{"pubkey":"0xac9b60d5afcbd5663a8a44b7c5a02f19e9a77ab0a35bd65809bb5c67ec582c897feb04decc694b13e08587f3ff9b5b60","validator_index":"3","committee_index":"0","committee_length":"13","validator_committee_index":"6","committees_at_slot":"1","slot":"8348"}],"execution_optimistic":false}` + "\n", | ||
}, | ||
{ | ||
name: "empty-index", | ||
epoch: strconv.FormatUint(fcu.HeadSlotVal/32, 10), | ||
code: http.StatusOK, | ||
reqBody: `[]`, | ||
expected: `{"data":[],"execution_optimistic":false}` + "\n", | ||
}, | ||
{ | ||
name: "404", | ||
reqBody: `["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]`, | ||
epoch: `999999999`, | ||
code: http.StatusBadRequest, | ||
}, | ||
} | ||
|
||
for _, c := range cases { | ||
t.Run(c.name, func(t *testing.T) { | ||
server := httptest.NewServer(handler.mux) | ||
defer server.Close() | ||
// | ||
body := bytes.Buffer{} | ||
body.WriteString(c.reqBody) | ||
// Query the block in the handler with /eth/v2/beacon/states/{block_id} with content-type octet-stream | ||
req, err := http.NewRequest("POST", server.URL+"/eth/v1/validator/duties/attester/"+c.epoch, &body) | ||
require.NoError(t, err) | ||
|
||
resp, err := http.DefaultClient.Do(req) | ||
require.NoError(t, err) | ||
|
||
defer resp.Body.Close() | ||
require.Equal(t, c.code, resp.StatusCode) | ||
if resp.StatusCode != http.StatusOK { | ||
return | ||
} | ||
// read the all of the octect | ||
out, err := io.ReadAll(resp.Body) | ||
require.NoError(t, err) | ||
require.Equal(t, c.expected, string(out)) | ||
}) | ||
} | ||
} |
Oops, something went wrong.