-
Notifications
You must be signed in to change notification settings - Fork 917
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change span representation to a struct in Slasher #4981
Changes from all commits
fad73c8
a2bef2d
62b52c3
890481c
2070ce4
7e8fd0e
ce5c542
ff3ebfd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,17 +2,13 @@ package attestations | |
|
||
import ( | ||
"context" | ||
"encoding/binary" | ||
"fmt" | ||
"sync" | ||
|
||
"github.com/pkg/errors" | ||
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" | ||
"github.com/prysmaticlabs/go-ssz" | ||
"github.com/prysmaticlabs/prysm/shared/params" | ||
"github.com/prysmaticlabs/prysm/slasher/detection/attestations/iface" | ||
"github.com/prysmaticlabs/prysm/slasher/detection/attestations/types" | ||
"github.com/prysmaticlabs/prysm/slasher/detection/filter" | ||
"go.opencensus.io/trace" | ||
) | ||
|
||
|
@@ -23,7 +19,7 @@ var _ = iface.SpanDetector(&SpanDetector{}) | |
// spans from validators and attestation data roots. | ||
type SpanDetector struct { | ||
// Slice of epochs for valindex => min-max span + double vote filter | ||
spans []map[uint64][3]uint16 | ||
spans []map[uint64]types.Span | ||
lock sync.RWMutex | ||
} | ||
|
||
|
@@ -32,7 +28,7 @@ type SpanDetector struct { | |
// the beacon state. | ||
func NewSpanDetector() *SpanDetector { | ||
return &SpanDetector{ | ||
spans: make([]map[uint64][3]uint16, 256), | ||
spans: make([]map[uint64]types.Span, 256), | ||
} | ||
} | ||
|
||
|
@@ -44,8 +40,8 @@ func (s *SpanDetector) DetectSlashingForValidator( | |
validatorIdx uint64, | ||
attData *ethpb.AttestationData, | ||
) (*types.DetectionResult, error) { | ||
ctx, span := trace.StartSpan(ctx, "detection.DetectSlashingForValidator") | ||
defer span.End() | ||
ctx, traceSpan := trace.StartSpan(ctx, "detection.DetectSlashingForValidator") | ||
defer traceSpan.End() | ||
sourceEpoch := attData.Source.Epoch | ||
targetEpoch := attData.Target.Epoch | ||
if (targetEpoch - sourceEpoch) > params.BeaconConfig().WeakSubjectivityPeriod { | ||
|
@@ -61,54 +57,48 @@ func (s *SpanDetector) DetectSlashingForValidator( | |
numSpans := uint64(len(s.spans)) | ||
|
||
if sp := s.spans[sourceEpoch%numSpans]; sp != nil { | ||
minSpan := sp[validatorIdx][0] | ||
minSpan := sp[validatorIdx].MinSpan | ||
if minSpan > 0 && minSpan < distance { | ||
slashableEpoch := sourceEpoch + uint64(minSpan) | ||
span := s.spans[slashableEpoch%numSpans][validatorIdx] | ||
return &types.DetectionResult{ | ||
Kind: types.SurroundVote, | ||
SlashableEpoch: sourceEpoch + uint64(minSpan), | ||
SigBytes: span.SigBytes, | ||
}, nil | ||
} | ||
|
||
maxSpan := sp[validatorIdx][1] | ||
maxSpan := sp[validatorIdx].MaxSpan | ||
if maxSpan > distance { | ||
slashableEpoch := sourceEpoch + uint64(maxSpan) | ||
span := s.spans[slashableEpoch%numSpans][validatorIdx] | ||
return &types.DetectionResult{ | ||
Kind: types.SurroundVote, | ||
SlashableEpoch: sourceEpoch + uint64(maxSpan), | ||
SlashableEpoch: slashableEpoch, | ||
SigBytes: span.SigBytes, | ||
}, nil | ||
} | ||
} | ||
|
||
if sp := s.spans[targetEpoch%numSpans]; sp != nil { | ||
filterNum := sp[validatorIdx][2] | ||
if filterNum == 0 { | ||
// Check if the validator has attested for this epoch or not. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We also need to check if it's the same dataroot in order to decide if its double. Doing it in another PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that will be handled by detection. This just makes sure the attestation triggers a hard DB check. |
||
if !sp[validatorIdx].HasAttested { | ||
return nil, nil | ||
} | ||
filterBytes := make([]byte, 2) | ||
binary.LittleEndian.PutUint16(filterBytes, filterNum) | ||
attFilter := filter.BloomFilter(filterBytes) | ||
|
||
attDataRoot, err := ssz.HashTreeRoot(attData) | ||
if err != nil { | ||
return nil, err | ||
} | ||
found, err := attFilter.Contains(attDataRoot[:]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if !found { | ||
return &types.DetectionResult{ | ||
Kind: types.DoubleVote, | ||
SlashableEpoch: targetEpoch, | ||
}, nil | ||
} | ||
return &types.DetectionResult{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems strange to return double vote when it is not verified There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just for the detection, it lets the detection service to do a hard check, which we always need to do anyways for checking double votes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using 2 bytes of the signature isn't safe enough to just compare the receiving attestation with it IMO, db checks are really quick with this strategy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thats true |
||
Kind: types.DoubleVote, | ||
SlashableEpoch: targetEpoch, | ||
SigBytes: sp[validatorIdx].SigBytes, | ||
}, nil | ||
} | ||
|
||
return nil, nil | ||
} | ||
|
||
// SpanForEpochByValidator returns the specific min-max span for a | ||
// validator index in a given epoch. | ||
func (s *SpanDetector) SpanForEpochByValidator(ctx context.Context, valIdx uint64, epoch uint64) ([3]uint16, error) { | ||
func (s *SpanDetector) SpanForEpochByValidator(ctx context.Context, valIdx uint64, epoch uint64) (types.Span, error) { | ||
ctx, span := trace.StartSpan(ctx, "detection.SpanForEpochByValidator") | ||
defer span.End() | ||
s.lock.RLock() | ||
|
@@ -118,13 +108,13 @@ func (s *SpanDetector) SpanForEpochByValidator(ctx context.Context, valIdx uint6 | |
if minMaxSpan, ok := span[valIdx]; ok { | ||
return minMaxSpan, nil | ||
} | ||
return [3]uint16{}, fmt.Errorf("validator index %d not found in span map", valIdx) | ||
return types.Span{}, fmt.Errorf("validator index %d not found in span map", valIdx) | ||
} | ||
return [3]uint16{}, fmt.Errorf("no data found for epoch %d", epoch) | ||
return types.Span{}, fmt.Errorf("no data found for epoch %d", epoch) | ||
} | ||
|
||
// ValidatorSpansByEpoch returns a list of all validator spans in a given epoch. | ||
func (s *SpanDetector) ValidatorSpansByEpoch(ctx context.Context, epoch uint64) map[uint64][3]uint16 { | ||
func (s *SpanDetector) ValidatorSpansByEpoch(ctx context.Context, epoch uint64) map[uint64]types.Span { | ||
ctx, span := trace.StartSpan(ctx, "detection.ValidatorSpansByEpoch") | ||
defer span.End() | ||
s.lock.RLock() | ||
|
@@ -160,50 +150,42 @@ func (s *SpanDetector) UpdateSpans(ctx context.Context, att *ethpb.IndexedAttest | |
// each validator in attesting indices. | ||
for i := 0; i < len(att.AttestingIndices); i++ { | ||
valIdx := att.AttestingIndices[i] | ||
// Mark the double vote filter. | ||
if err := s.markAttFilter(att.Data, valIdx); err != nil { | ||
return errors.Wrap(err, "failed to update attestation filter") | ||
} | ||
// Save the signature for the received attestation so we can have more detail to find it in the DB. | ||
s.saveSigBytes(att, valIdx) | ||
// Update min and max spans. | ||
s.updateMinSpan(source, target, valIdx) | ||
s.updateMaxSpan(source, target, valIdx) | ||
} | ||
return nil | ||
} | ||
|
||
// markAttFilter sets the third uint16 in the target epochs span to a bloom filter | ||
// with the attestation data root as the key in set. After creating the []byte for the bloom filter, | ||
// it encoded into a uint16 to keep the data structure for the spanner simple and clean. | ||
// A bloom filter is used to prevent collision when using such a small data size. | ||
func (s *SpanDetector) markAttFilter(attData *ethpb.AttestationData, valIdx uint64) error { | ||
// saveSigBytes saves the first 2 bytes of the signature for the att we're updating the spans to. | ||
// Later used to help us find the violating attestation in the DB. | ||
func (s *SpanDetector) saveSigBytes(att *ethpb.IndexedAttestation, valIdx uint64) { | ||
numSpans := uint64(len(s.spans)) | ||
target := attData.Target.Epoch | ||
target := att.Data.Target.Epoch | ||
|
||
// Check if there is an existing bloom filter, if so, don't modify it. | ||
// Check if there is already info saved in span[2]. | ||
if sp := s.spans[target%numSpans]; sp == nil { | ||
s.spans[target%numSpans] = make(map[uint64][3]uint16) | ||
s.spans[target%numSpans] = make(map[uint64]types.Span) | ||
} | ||
filterNum := s.spans[target%numSpans][valIdx][2] | ||
if filterNum != 0 { | ||
return nil | ||
// If the validator has already attested for this target epoch, | ||
if s.spans[target%numSpans][valIdx].HasAttested { | ||
return | ||
} | ||
|
||
// Generate the attestation data root and use it as the only key in the bloom filter. | ||
attDataRoot, err := ssz.HashTreeRoot(attData) | ||
if err != nil { | ||
return err | ||
sigBytes := [2]byte{0, 0} | ||
if len(att.Signature) > 1 { | ||
sigBytes = [2]byte{att.Signature[0], att.Signature[1]} | ||
} | ||
attFilter, err := filter.NewBloomFilter(attDataRoot[:]) | ||
if err != nil { | ||
return err | ||
// Save the signature bytes into the span for this epoch. | ||
span := s.spans[target%numSpans][valIdx] | ||
s.spans[target%numSpans][valIdx] = types.Span{ | ||
MinSpan: span.MinSpan, | ||
MaxSpan: span.MaxSpan, | ||
SigBytes: sigBytes, | ||
HasAttested: true, | ||
} | ||
filterNum = binary.LittleEndian.Uint16(attFilter) | ||
|
||
// Set the bloom filter back into the span for the epoch. | ||
minSpan := s.spans[target%numSpans][valIdx][0] | ||
maxSpan := s.spans[target%numSpans][valIdx][1] | ||
s.spans[target%numSpans][valIdx] = [3]uint16{minSpan, maxSpan, filterNum} | ||
return nil | ||
} | ||
|
||
// Updates a min span for a validator index given a source and target epoch | ||
|
@@ -216,15 +198,17 @@ func (s *SpanDetector) updateMinSpan(source uint64, target uint64, valIdx uint64 | |
for epochInt := int64(source - 1); epochInt >= 0; epochInt-- { | ||
epoch := uint64(epochInt) | ||
if sp := s.spans[epoch%numSpans]; sp == nil { | ||
s.spans[epoch%numSpans] = make(map[uint64][3]uint16) | ||
s.spans[epoch%numSpans] = make(map[uint64]types.Span) | ||
} | ||
newMinSpan := uint16(target - epoch) | ||
minSpan := s.spans[epoch%numSpans][valIdx][0] | ||
maxSpan := s.spans[epoch%numSpans][valIdx][1] | ||
attFilter := s.spans[epoch%numSpans][valIdx][2] | ||
if minSpan == 0 || minSpan > newMinSpan { | ||
fmt.Printf("epoch %d, valIdx %d: %v\n", epoch%numSpans, valIdx, [3]uint16{newMinSpan, maxSpan, attFilter}) | ||
s.spans[epoch%numSpans][valIdx] = [3]uint16{newMinSpan, maxSpan, attFilter} | ||
span := s.spans[epoch%numSpans][valIdx] | ||
if span.MinSpan == 0 || span.MinSpan > newMinSpan { | ||
s.spans[epoch%numSpans][valIdx] = types.Span{ | ||
MinSpan: newMinSpan, | ||
MaxSpan: span.MaxSpan, | ||
SigBytes: span.SigBytes, | ||
HasAttested: span.HasAttested, | ||
} | ||
} else { | ||
break | ||
} | ||
|
@@ -237,14 +221,17 @@ func (s *SpanDetector) updateMaxSpan(source uint64, target uint64, valIdx uint64 | |
numSpans := uint64(len(s.spans)) | ||
for epoch := source + 1; epoch < target; epoch++ { | ||
if sp := s.spans[epoch%numSpans]; sp == nil { | ||
s.spans[epoch%numSpans] = make(map[uint64][3]uint16) | ||
s.spans[epoch%numSpans] = make(map[uint64]types.Span) | ||
} | ||
minSpan := s.spans[epoch%numSpans][valIdx][0] | ||
maxSpan := s.spans[epoch%numSpans][valIdx][1] | ||
attFilter := s.spans[epoch%numSpans][valIdx][2] | ||
span := s.spans[epoch%numSpans][valIdx] | ||
newMaxSpan := uint16(target - epoch) | ||
if newMaxSpan > maxSpan { | ||
s.spans[epoch%numSpans][valIdx] = [3]uint16{minSpan, newMaxSpan, attFilter} | ||
if newMaxSpan > span.MaxSpan { | ||
s.spans[epoch%numSpans][valIdx] = types.Span{ | ||
MinSpan: span.MinSpan, | ||
MaxSpan: newMaxSpan, | ||
SigBytes: span.SigBytes, | ||
HasAttested: span.HasAttested, | ||
} | ||
} else { | ||
break | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not using a reference since the actual struct nearly the same size as the pointer would be, just an extra bool. There are also no nested/dynamic types. Size is static.
If this should be changed let me know.