Skip to content

Commit

Permalink
Detect Proposer Slashing Implementation (#5139)
Browse files Browse the repository at this point in the history
* detect blocks

* detect blocks

* use stub

* use stub

* use stub

* todo

* fix test

* add tests and utils

* fix imports

* fix imports

* fix comment

* todo

* proposerIndex

* fix broken test

* formatting and simplified if

* Update slasher/detection/service.go

* Update slasher/detection/testing/utils.go

Co-Authored-By: terence tsao <[email protected]>

* fixed up final comments

* better naming

* Update slasher/detection/service.go

* Update slasher/detection/service.go

* Update slasher/detection/service.go

Co-Authored-By: Ivan Martinez <[email protected]>

* no more named args

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
Co-authored-by: Raul Jordan <[email protected]>
Co-authored-by: terence tsao <[email protected]>
Co-authored-by: Ivan Martinez <[email protected]>
  • Loading branch information
5 people authored Apr 2, 2020
1 parent e75abe8 commit 9804bcb
Show file tree
Hide file tree
Showing 14 changed files with 234 additions and 101 deletions.
4 changes: 2 additions & 2 deletions slasher/db/iface/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ type WriteAccessDatabase interface {
SetLatestEpochDetected(ctx context.Context, epoch uint64) error

// BlockHeader related methods.
SaveBlockHeader(ctx context.Context, validatorID uint64, blockHeader *ethpb.SignedBeaconBlockHeader) error
DeleteBlockHeader(ctx context.Context, validatorID uint64, blockHeader *ethpb.SignedBeaconBlockHeader) error
SaveBlockHeader(ctx context.Context, blockHeader *ethpb.SignedBeaconBlockHeader) error
DeleteBlockHeader(ctx context.Context, blockHeader *ethpb.SignedBeaconBlockHeader) error
PruneBlockHistory(ctx context.Context, currentEpoch uint64, pruningEpochAge uint64) error

// IndexedAttestations related methods.
Expand Down
8 changes: 4 additions & 4 deletions slasher/db/kv/block_header.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ func (db *Store) HasBlockHeader(ctx context.Context, epoch uint64, validatorID u
}

// SaveBlockHeader accepts a block header and writes it to disk.
func (db *Store) SaveBlockHeader(ctx context.Context, validatorID uint64, blockHeader *ethpb.SignedBeaconBlockHeader) error {
func (db *Store) SaveBlockHeader(ctx context.Context, blockHeader *ethpb.SignedBeaconBlockHeader) error {
ctx, span := trace.StartSpan(ctx, "slasherDB.SaveBlockHeader")
defer span.End()
epoch := helpers.SlotToEpoch(blockHeader.Header.Slot)
key := encodeEpochValidatorIDSig(epoch, validatorID, blockHeader.Signature)
key := encodeEpochValidatorIDSig(epoch, blockHeader.Header.ProposerIndex, blockHeader.Signature)
enc, err := proto.Marshal(blockHeader)
if err != nil {
return errors.Wrap(err, "failed to encode block")
Expand All @@ -97,11 +97,11 @@ func (db *Store) SaveBlockHeader(ctx context.Context, validatorID uint64, blockH
}

// DeleteBlockHeader deletes a block header using the epoch and validator id.
func (db *Store) DeleteBlockHeader(ctx context.Context, validatorID uint64, blockHeader *ethpb.SignedBeaconBlockHeader) error {
func (db *Store) DeleteBlockHeader(ctx context.Context, blockHeader *ethpb.SignedBeaconBlockHeader) error {
ctx, span := trace.StartSpan(ctx, "slasherDB.DeleteBlockHeader")
defer span.End()
epoch := helpers.SlotToEpoch(blockHeader.Header.Slot)
key := encodeEpochValidatorIDSig(epoch, validatorID, blockHeader.Signature)
key := encodeEpochValidatorIDSig(epoch, blockHeader.Header.ProposerIndex, blockHeader.Signature)
return db.update(func(tx *bolt.Tx) error {
bucket := tx.Bucket(historicBlockHeadersBucket)
if err := bucket.Delete(key); err != nil {
Expand Down
81 changes: 32 additions & 49 deletions slasher/db/kv/block_header_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,30 +43,27 @@ func TestSaveHistoryBlkHdr(t *testing.T) {
ctx := context.Background()

tests := []struct {
vID uint64
bh *ethpb.SignedBeaconBlockHeader
bh *ethpb.SignedBeaconBlockHeader
}{
{
vID: uint64(0),
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in"), Header: &ethpb.BeaconBlockHeader{Slot: 0}},
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in"), Header: &ethpb.BeaconBlockHeader{Slot: 0, ProposerIndex: 0}},
},
{
vID: uint64(1),
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 2nd"), Header: &ethpb.BeaconBlockHeader{Slot: 0}},
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 2nd"), Header: &ethpb.BeaconBlockHeader{Slot: 0, ProposerIndex: 1}},
},
{
vID: uint64(0),
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 3rd"), Header: &ethpb.BeaconBlockHeader{Slot: params.BeaconConfig().SlotsPerEpoch + 1}},

bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 3rd"), Header: &ethpb.BeaconBlockHeader{Slot: params.BeaconConfig().SlotsPerEpoch + 1, ProposerIndex: 0}},
},
}

for _, tt := range tests {
err := db.SaveBlockHeader(ctx, tt.vID, tt.bh)
err := db.SaveBlockHeader(ctx, tt.bh)
if err != nil {
t.Fatalf("save block failed: %v", err)
}

bha, err := db.BlockHeaders(ctx, helpers.SlotToEpoch(tt.bh.Header.Slot), tt.vID)
bha, err := db.BlockHeaders(ctx, helpers.SlotToEpoch(tt.bh.Header.Slot), tt.bh.Header.ProposerIndex)
if err != nil {
t.Fatalf("failed to get block: %v", err)
}
Expand All @@ -86,44 +83,40 @@ func TestDeleteHistoryBlkHdr(t *testing.T) {
ctx := context.Background()

tests := []struct {
vID uint64
bh *ethpb.SignedBeaconBlockHeader
bh *ethpb.SignedBeaconBlockHeader
}{
{
vID: uint64(0),
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in"), Header: &ethpb.BeaconBlockHeader{Slot: 0}},
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in"), Header: &ethpb.BeaconBlockHeader{Slot: 0, ProposerIndex: 0}},
},
{
vID: uint64(1),
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 2nd"), Header: &ethpb.BeaconBlockHeader{Slot: 0}},
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 2nd"), Header: &ethpb.BeaconBlockHeader{Slot: 0, ProposerIndex: 1}},
},
{
vID: uint64(0),
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 3rd"), Header: &ethpb.BeaconBlockHeader{Slot: params.BeaconConfig().SlotsPerEpoch + 1}},
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 3rd"), Header: &ethpb.BeaconBlockHeader{Slot: params.BeaconConfig().SlotsPerEpoch + 1, ProposerIndex: 0}},
},
}
for _, tt := range tests {

err := db.SaveBlockHeader(ctx, tt.vID, tt.bh)
err := db.SaveBlockHeader(ctx, tt.bh)
if err != nil {
t.Fatalf("save block failed: %v", err)
}
}

for _, tt := range tests {
bha, err := db.BlockHeaders(ctx, helpers.SlotToEpoch(tt.bh.Header.Slot), tt.vID)
bha, err := db.BlockHeaders(ctx, helpers.SlotToEpoch(tt.bh.Header.Slot), tt.bh.Header.ProposerIndex)
if err != nil {
t.Fatalf("failed to get block: %v", err)
}

if bha == nil || !reflect.DeepEqual(bha[0], tt.bh) {
t.Fatalf("get should return bh: %v", bha)
}
err = db.DeleteBlockHeader(ctx, tt.vID, tt.bh)
err = db.DeleteBlockHeader(ctx, tt.bh)
if err != nil {
t.Fatalf("save block failed: %v", err)
}
bh, err := db.BlockHeaders(ctx, helpers.SlotToEpoch(tt.bh.Header.Slot), tt.vID)
bh, err := db.BlockHeaders(ctx, helpers.SlotToEpoch(tt.bh.Header.Slot), tt.bh.Header.ProposerIndex)

if err != nil {
t.Fatal(err)
Expand All @@ -144,40 +137,36 @@ func TestHasHistoryBlkHdr(t *testing.T) {
ctx := context.Background()

tests := []struct {
vID uint64
bh *ethpb.SignedBeaconBlockHeader
bh *ethpb.SignedBeaconBlockHeader
}{
{
vID: uint64(0),
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in"), Header: &ethpb.BeaconBlockHeader{Slot: 0}},
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in"), Header: &ethpb.BeaconBlockHeader{Slot: 0, ProposerIndex: 0}},
},
{
vID: uint64(1),
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 2nd"), Header: &ethpb.BeaconBlockHeader{Slot: 0}},
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 2nd"), Header: &ethpb.BeaconBlockHeader{Slot: 0, ProposerIndex: 1}},
},
{
vID: uint64(0),
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 3rd"), Header: &ethpb.BeaconBlockHeader{Slot: params.BeaconConfig().SlotsPerEpoch + 1}},
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 3rd"), Header: &ethpb.BeaconBlockHeader{Slot: params.BeaconConfig().SlotsPerEpoch + 1, ProposerIndex: 0}},
},
}
for _, tt := range tests {

found := db.HasBlockHeader(ctx, helpers.SlotToEpoch(tt.bh.Header.Slot), tt.vID)
found := db.HasBlockHeader(ctx, helpers.SlotToEpoch(tt.bh.Header.Slot), tt.bh.Header.ProposerIndex)
if found {
t.Fatal("has block header should return false for block headers that are not in db")
}
err := db.SaveBlockHeader(ctx, tt.vID, tt.bh)
err := db.SaveBlockHeader(ctx, tt.bh)
if err != nil {
t.Fatalf("save block failed: %v", err)
}
}
for _, tt := range tests {
err := db.SaveBlockHeader(ctx, tt.vID, tt.bh)
err := db.SaveBlockHeader(ctx, tt.bh)
if err != nil {
t.Fatalf("save block failed: %v", err)
}

found := db.HasBlockHeader(ctx, helpers.SlotToEpoch(tt.bh.Header.Slot), tt.vID)
found := db.HasBlockHeader(ctx, helpers.SlotToEpoch(tt.bh.Header.Slot), tt.bh.Header.ProposerIndex)

if !found {
t.Fatal("has block header should return true")
Expand All @@ -193,38 +182,32 @@ func TestPruneHistoryBlkHdr(t *testing.T) {
ctx := context.Background()

tests := []struct {
vID uint64
bh *ethpb.SignedBeaconBlockHeader
bh *ethpb.SignedBeaconBlockHeader
}{
{
vID: uint64(0),
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in"), Header: &ethpb.BeaconBlockHeader{Slot: 0}},
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in"), Header: &ethpb.BeaconBlockHeader{Slot: 0, ProposerIndex: 0}},
},
{
vID: uint64(1),
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 2nd"), Header: &ethpb.BeaconBlockHeader{Slot: 0}},
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 2nd"), Header: &ethpb.BeaconBlockHeader{Slot: 0, ProposerIndex: 1}},
},
{
vID: uint64(0),
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 3rd"), Header: &ethpb.BeaconBlockHeader{Slot: params.BeaconConfig().SlotsPerEpoch + 1}},
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 3rd"), Header: &ethpb.BeaconBlockHeader{Slot: params.BeaconConfig().SlotsPerEpoch + 1, ProposerIndex: 0}},
},
{
vID: uint64(0),
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 4th"), Header: &ethpb.BeaconBlockHeader{Slot: params.BeaconConfig().SlotsPerEpoch*2 + 1}},
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 4th"), Header: &ethpb.BeaconBlockHeader{Slot: params.BeaconConfig().SlotsPerEpoch*2 + 1, ProposerIndex: 0}},
},
{
vID: uint64(0),
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 5th"), Header: &ethpb.BeaconBlockHeader{Slot: params.BeaconConfig().SlotsPerEpoch*3 + 1}},
bh: &ethpb.SignedBeaconBlockHeader{Signature: []byte("let me in 5th"), Header: &ethpb.BeaconBlockHeader{Slot: params.BeaconConfig().SlotsPerEpoch*3 + 1, ProposerIndex: 0}},
},
}

for _, tt := range tests {
err := db.SaveBlockHeader(ctx, tt.vID, tt.bh)
err := db.SaveBlockHeader(ctx, tt.bh)
if err != nil {
t.Fatalf("save block header failed: %v", err)
}

bha, err := db.BlockHeaders(ctx, helpers.SlotToEpoch(tt.bh.Header.Slot), tt.vID)
bha, err := db.BlockHeaders(ctx, helpers.SlotToEpoch(tt.bh.Header.Slot), tt.bh.Header.ProposerIndex)
if err != nil {
t.Fatalf("failed to get block header: %v", err)
}
Expand All @@ -241,7 +224,7 @@ func TestPruneHistoryBlkHdr(t *testing.T) {
}

for _, tt := range tests {
bha, err := db.BlockHeaders(ctx, helpers.SlotToEpoch(tt.bh.Header.Slot), tt.vID)
bha, err := db.BlockHeaders(ctx, helpers.SlotToEpoch(tt.bh.Header.Slot), tt.bh.Header.ProposerIndex)
if err != nil {
t.Fatalf("failed to get block header: %v", err)
}
Expand Down
3 changes: 3 additions & 0 deletions slasher/detection/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ go_library(
"@com_github_prometheus_client_golang//prometheus:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
"@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library",
"@com_github_prysmaticlabs_go_ssz//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
],
Expand All @@ -46,6 +47,8 @@ go_test(
"//slasher/db/testing:go_default_library",
"//slasher/db/types:go_default_library",
"//slasher/detection/attestations:go_default_library",
"//slasher/detection/proposals:go_default_library",
"//slasher/detection/testing:go_default_library",
"@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
Expand Down
9 changes: 9 additions & 0 deletions slasher/detection/detect.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package detection

import (
"bytes"
"context"

"github.com/gogo/protobuf/proto"
Expand Down Expand Up @@ -163,6 +164,14 @@ func (ds *Service) DetectDoubleProposals(ctx context.Context, incomingBlock *eth
return ds.proposalsDetector.DetectDoublePropose(ctx, incomingBlock)
}

func isDoublePropose(
incomingBlockHeader *ethpb.SignedBeaconBlockHeader,
prevBlockHeader *ethpb.SignedBeaconBlockHeader,
) bool {
return incomingBlockHeader.Header.ProposerIndex == prevBlockHeader.Header.ProposerIndex &&
!bytes.Equal(incomingBlockHeader.Signature, prevBlockHeader.Signature)
}

func isDoubleVote(incomingAtt *ethpb.IndexedAttestation, prevAtt *ethpb.IndexedAttestation) bool {
return !proto.Equal(incomingAtt.Data, prevAtt.Data) && incomingAtt.Data.Target.Epoch == prevAtt.Data.Target.Epoch
}
Expand Down
80 changes: 80 additions & 0 deletions slasher/detection/detect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ package detection

import (
"context"
"reflect"
"testing"

ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
testDB "github.com/prysmaticlabs/prysm/slasher/db/testing"
status "github.com/prysmaticlabs/prysm/slasher/db/types"
"github.com/prysmaticlabs/prysm/slasher/detection/attestations"
"github.com/prysmaticlabs/prysm/slasher/detection/proposals"
testDetect "github.com/prysmaticlabs/prysm/slasher/detection/testing"
)

func TestDetect_detectAttesterSlashings_Surround(t *testing.T) {
Expand Down Expand Up @@ -341,3 +344,80 @@ func TestDetect_detectAttesterSlashings_Double(t *testing.T) {
})
}
}

func TestDetect_detectProposerSlashing(t *testing.T) {
type testStruct struct {
name string
blk *ethpb.SignedBeaconBlockHeader
incomingBlk *ethpb.SignedBeaconBlockHeader
slashing *ethpb.ProposerSlashing
}
blk1epoch0, err := testDetect.SignedBlockHeader(testDetect.StartSlot(0), 0)
if err != nil {
t.Fatal(err)
}
blk2epoch0, err := testDetect.SignedBlockHeader(testDetect.StartSlot(0)+1, 0)
if err != nil {
t.Fatal(err)
}
blk1epoch1, err := testDetect.SignedBlockHeader(testDetect.StartSlot(1), 0)
if err != nil {
t.Fatal(err)
}
tests := []testStruct{
{
name: "same block sig dont slash",
blk: blk1epoch0,
incomingBlk: blk1epoch0,
slashing: nil,
},
{
name: "block from different epoch dont slash",
blk: blk1epoch0,
incomingBlk: blk1epoch1,
slashing: nil,
},
{
name: "different sig from same epoch slash",
blk: blk1epoch0,
incomingBlk: blk2epoch0,
slashing: &ethpb.ProposerSlashing{Header_1: blk2epoch0, Header_2: blk1epoch0},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
db := testDB.SetupSlasherDB(t, false)
defer testDB.TeardownSlasherDB(t, db)
ctx := context.Background()
ds := Service{
ctx: ctx,
slasherDB: db,
proposalsDetector: proposals.NewProposeDetector(db),
}
if err := db.SaveBlockHeader(ctx, tt.blk); err != nil {
t.Fatal(err)
}

slashing, err := ds.proposalsDetector.DetectDoublePropose(ctx, tt.incomingBlk)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(slashing, tt.slashing) {
t.Errorf("Wanted: %v, received %v", tt.slashing, slashing)
}
savedSlashings, err := db.ProposalSlashingsByStatus(ctx, status.Active)
if tt.slashing != nil && len(savedSlashings) != 1 {
t.Fatalf("Did not save slashing to db")
}

if slashing != nil && !isDoublePropose(slashing.Header_1, slashing.Header_2) {
t.Fatalf(
"Expected slashing to be valid, received atts with target epoch %v and %v but not valid",
slashing.Header_1,
slashing.Header_2,
)
}

})
}
}
Loading

0 comments on commit 9804bcb

Please sign in to comment.