Compare commits

...

2 Commits

Author SHA1 Message Date
Bastin
2dacc39854 slashing detection algorithm 2025-04-19 00:42:24 +02:00
Bastin
96c19a1836 get indexed attestations for epoch 2025-04-18 16:58:07 +02:00

View File

@@ -7,18 +7,21 @@ import (
"fmt"
"io"
"net/http"
"sort"
"strconv"
"strings"
"github.com/OffchainLabs/prysm/v6/api"
"github.com/OffchainLabs/prysm/v6/api/server/structs"
"github.com/OffchainLabs/prysm/v6/beacon-chain/cache/depositsnapshot"
coreHelpers "github.com/OffchainLabs/prysm/v6/beacon-chain/core/helpers"
corehelpers "github.com/OffchainLabs/prysm/v6/beacon-chain/core/helpers"
"github.com/OffchainLabs/prysm/v6/beacon-chain/core/transition"
"github.com/OffchainLabs/prysm/v6/beacon-chain/db/filters"
"github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/helpers"
"github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/eth/shared"
"github.com/OffchainLabs/prysm/v6/beacon-chain/rpc/prysm/v1alpha1/validator"
"github.com/OffchainLabs/prysm/v6/beacon-chain/slasher/types"
fieldparams "github.com/OffchainLabs/prysm/v6/config/fieldparams"
"github.com/OffchainLabs/prysm/v6/config/params"
"github.com/OffchainLabs/prysm/v6/consensus-types/blocks"
@@ -28,6 +31,7 @@ import (
"github.com/OffchainLabs/prysm/v6/monitoring/tracing/trace"
"github.com/OffchainLabs/prysm/v6/network/httputil"
eth "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1/attestation"
"github.com/OffchainLabs/prysm/v6/runtime/version"
"github.com/OffchainLabs/prysm/v6/time/slots"
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -1738,3 +1742,131 @@ func serializeItems[T interface{ MarshalSSZ() ([]byte, error) }](items []T) ([]b
}
return result, nil
}
func (s *Server) GetAttestashionSlashings(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "beacon.GetAttestashionSlashings")
defer span.End()
startEpoch := r.PathValue("start_epoch")
if startEpoch == "" {
httputil.HandleError(w, "startEpoch is required in URL params", http.StatusBadRequest)
return
}
startEpochInt, err := strconv.ParseUint(startEpoch, 10, 64)
if err != nil {
httputil.HandleError(w, "Could not parse startEpoch: "+err.Error(), http.StatusBadRequest)
return
}
endEpoch := r.PathValue("end_epoch")
if endEpoch == "" {
httputil.HandleError(w, "endEpoch is required in URL params", http.StatusBadRequest)
return
}
endEpochInt, err := strconv.ParseUint(endEpoch, 10, 64)
if err != nil {
httputil.HandleError(w, "Could not parse endEpoch: "+err.Error(), http.StatusBadRequest)
return
}
startSlot, err := slots.EpochStart(primitives.Epoch(startEpochInt))
if err != nil {
httputil.HandleError(w, "Could not get start_epoch start slot: "+err.Error(), http.StatusInternalServerError)
return
}
endSlot, err := slots.EpochEnd(primitives.Epoch(endEpochInt))
if err != nil {
httputil.HandleError(w, "Could not get end_epoch end slot: "+err.Error(), http.StatusInternalServerError)
return
}
// Fetch all seen attestations from the database and convert them to indexed attestations
var indexedAtts []*types.WrappedIndexedAtt
for slot := startSlot; slot <= endSlot; slot++ {
blks, err := s.BeaconDB.BlocksBySlot(ctx, slot)
if err != nil {
continue
}
for _, blk := range blks {
atts := blk.Block().Body().Attestations()
preState, err := s.StateGenService.StateByRoot(ctx, blk.Block().ParentRoot())
if err != nil {
httputil.HandleError(w, "Could not get pre-state: "+err.Error(), http.StatusInternalServerError)
return
}
for _, att := range atts {
committees, err := coreHelpers.AttestationCommitteesFromState(ctx, preState, att)
if err != nil {
httputil.HandleError(w, "Could not get attestation committees: "+err.Error(), http.StatusInternalServerError)
return
}
indexedAtt, err := attestation.ConvertToIndexed(ctx, att, committees...)
if err != nil {
httputil.HandleError(w, "Could not convert to indexed attestation: "+err.Error(), http.StatusInternalServerError)
return
}
indexedAtts = append(indexedAtts, &types.WrappedIndexedAtt{IndexedAtt: indexedAtt})
}
}
}
slashingsPerVal, err := detectSlashings(indexedAtts)
fmt.Println(slashingsPerVal)
}
type AttestationSlashing struct {
A1 *types.WrappedIndexedAtt
A2 *types.WrappedIndexedAtt
}
func detectSlashings(indexedAtts []*types.WrappedIndexedAtt) (map[uint64][]*AttestationSlashing, error) {
// Group attestations by validator index
perVal := make(map[uint64][]*types.WrappedIndexedAtt)
for _, att := range indexedAtts {
for _, vid := range att.GetAttestingIndices() {
perVal[vid] = append(perVal[vid], att)
}
}
slashingsPerVal := make(map[uint64][]*AttestationSlashing)
// Detect slashings
for vid, atts := range perVal {
// double vote
seen := make(map[uint64]*types.WrappedIndexedAtt)
for _, att := range atts {
if prev, ok := seen[uint64(att.GetData().Target.Epoch)]; ok {
if uint64(prev.GetData().Source.Epoch) != uint64(att.GetData().Source.Epoch) {
slashingsPerVal[vid] = append(slashingsPerVal[vid], &AttestationSlashing{A1: prev, A2: att})
}
} else {
seen[uint64(att.GetData().Target.Epoch)] = att
}
}
// surround vote
// sort by source and target
sort.Slice(atts, func(i, j int) bool {
if uint64(atts[i].GetData().Source.Epoch) != uint64(atts[j].GetData().Source.Epoch) {
return uint64(atts[i].GetData().Source.Epoch) < uint64(atts[j].GetData().Source.Epoch)
}
return uint64(atts[i].GetData().Target.Epoch) < uint64(atts[j].GetData().Target.Epoch)
})
maxAtt := atts[0]
for _, att := range atts[1:] {
if uint64(att.GetData().Target.Epoch) < uint64(maxAtt.GetData().Source.Epoch) {
slashingsPerVal[vid] = append(slashingsPerVal[vid], &AttestationSlashing{A1: maxAtt, A2: att})
}
if uint64(att.GetData().Target.Epoch) > uint64(maxAtt.GetData().Target.Epoch) {
maxAtt = att
}
}
}
return slashingsPerVal, nil
}