erigon-pulse/cl/beacon/handler/duties_attester.go
2024-01-01 22:18:11 +01:00

164 lines
5.7 KiB
Go

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
}