Skip to content
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

IsSlashableBlock rpc endpoint implementation #5765

Merged
merged 17 commits into from
May 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions beacon-chain/core/blocks/block_operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,16 +203,16 @@ func ProcessBlockHeader(
}

// Verify proposer signature.
if err := VerifyBlockHeaderSignature(beaconState, block); err != nil {
if err := VerifyBlockSignature(beaconState, block); err != nil {
return nil, err
}

return beaconState, nil
}

// VerifyBlockHeaderSignature verifies the proposer signature of a beacon block.
func VerifyBlockHeaderSignature(beaconState *stateTrie.BeaconState, block *ethpb.SignedBeaconBlock) error {
proposer, err := beaconState.ValidatorAtIndexReadOnly(block.Block.ProposerIndex)
// VerifyBlockSignature verifies the proposer signature of a beacon block.
func VerifyBlockSignature(beaconState *stateTrie.BeaconState, block *ethpb.SignedBeaconBlock) error {
proposer, err := beaconState.ValidatorAtIndex(block.Block.ProposerIndex)
if err != nil {
return err
}
Expand All @@ -222,7 +222,7 @@ func VerifyBlockHeaderSignature(beaconState *stateTrie.BeaconState, block *ethpb
if err != nil {
return err
}
proposerPubKey := proposer.PublicKey()
proposerPubKey := proposer.PublicKey
return helpers.VerifyBlockSigningRoot(block.Block, proposerPubKey[:], block.Signature, domain)
}

Expand Down
22 changes: 22 additions & 0 deletions beacon-chain/core/helpers/signing_root.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,28 @@ func VerifyBlockSigningRoot(blk *ethpb.BeaconBlock, pub []byte, signature []byte
return nil
}

// VerifyBlockHeaderSigningRoot verifies the signing root of a block header given it's public key, signature and domain.
func VerifyBlockHeaderSigningRoot(blkHdr *ethpb.BeaconBlockHeader, pub []byte, signature []byte, domain []byte) error {
publicKey, err := bls.PublicKeyFromBytes(pub)
if err != nil {
return errors.Wrap(err, "could not convert bytes to public key")
}
sig, err := bls.SignatureFromBytes(signature)
if err != nil {
return errors.Wrap(err, "could not convert bytes to signature")
}
root, err := signingRoot(func() ([32]byte, error) {
return stateutil.BlockHeaderRoot(blkHdr)
}, domain)
if err != nil {
return errors.Wrap(err, "could not compute signing root")
}
if !sig.Verify(root[:], publicKey) {
return ErrSigFailedToVerify
}
return nil
}

// ComputeDomain returns the domain version for BLS private key to sign and verify with a zeroed 4-byte
// array as the fork version.
//
Expand Down
2 changes: 1 addition & 1 deletion beacon-chain/sync/validate_beacon_blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func (r *Service) validateBeaconBlockPubSub(ctx context.Context, pid peer.ID, ms
return false
}

if err := blocks.VerifyBlockHeaderSignature(parentState, blk); err != nil {
if err := blocks.VerifyBlockSignature(parentState, blk); err != nil {
log.WithError(err).WithField("blockSlot", blk.Block.Slot).Warn("Could not verify block signature")
return false
}
Expand Down
22 changes: 13 additions & 9 deletions slasher/beaconclient/chain_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,25 @@ func (bs *Service) ChainHead(
return res, nil
}

// GenesisValidatorsRoot requests the beacon chain genesis validators
// root via gRPC.
// GenesisValidatorsRoot requests or fetch from memory the beacon chain genesis
// validators root via gRPC.
func (bs *Service) GenesisValidatorsRoot(
ctx context.Context,
) ([]byte, error) {
ctx, span := trace.StartSpan(ctx, "beaconclient.GenesisValidatorsRoot")
defer span.End()
res, err := bs.nodeClient.GetGenesis(ctx, &ptypes.Empty{})
if err != nil {
return nil, errors.Wrap(err, "Could not retrieve genesis data")
}
if res == nil {
return nil, errors.Wrap(err, " genesis data")

if bs.genesisValidatorRoot == nil {
res, err := bs.nodeClient.GetGenesis(ctx, &ptypes.Empty{})
if err != nil {
return nil, errors.Wrap(err, "could not retrieve genesis data")
}
if res == nil {
return nil, errors.Wrap(err, "nil genesis data")
}
bs.genesisValidatorRoot = res.GenesisValidatorsRoot
}
return res.GenesisValidatorsRoot, nil
return bs.genesisValidatorRoot, nil
}

// Poll the beacon node every syncStatusPollingInterval until the node
Expand Down
8 changes: 8 additions & 0 deletions slasher/beaconclient/chain_data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ func TestService_GenesisValidatorsRoot(t *testing.T) {
if !bytes.Equal(res, wanted.GenesisValidatorsRoot) {
t.Errorf("Wanted %#x, received %#x", wanted.GenesisValidatorsRoot, res)
}
// test next fetch uses memory and not the rpc call.
res, err = bs.GenesisValidatorsRoot(context.Background())
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(res, wanted.GenesisValidatorsRoot) {
t.Errorf("Wanted %#x, received %#x", wanted.GenesisValidatorsRoot, res)
}
}

func TestService_QuerySyncStatus(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions slasher/beaconclient/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type Service struct {
receivedAttestationsBuffer chan *ethpb.IndexedAttestation
collectedAttestationsBuffer chan []*ethpb.IndexedAttestation
publicKeyCache *cache.PublicKeyCache
genesisValidatorRoot []byte
}

// Config options for the beaconclient service.
Expand Down
3 changes: 3 additions & 0 deletions slasher/detection/proposals/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,8 @@ func (dd *ProposeDetector) DetectDoublePropose(
}
return ps, nil
}
if err := dd.slasherDB.SaveBlockHeader(ctx, incomingBlk); err != nil {
return nil, err
}
return nil, nil
}
2 changes: 2 additions & 0 deletions slasher/rpc/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ go_test(
embed = [":go_default_library"],
deps = [
"//beacon-chain/core/helpers:go_default_library",
"//beacon-chain/state/stateutil:go_default_library",
"//shared/bls:go_default_library",
"//shared/bytesutil:go_default_library",
"//shared/mock:go_default_library",
"//shared/p2putils:go_default_library",
"//shared/params:go_default_library",
Expand Down
61 changes: 60 additions & 1 deletion slasher/rpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,23 @@ type Server struct {
func (ss *Server) IsSlashableAttestation(ctx context.Context, req *ethpb.IndexedAttestation) (*slashpb.AttesterSlashingResponse, error) {
ctx, span := trace.StartSpan(ctx, "detection.IsSlashableAttestation")
defer span.End()

if req == nil {
return nil, status.Error(codes.InvalidArgument, "nil request provided")
}
if req.Data == nil {
return nil, status.Error(codes.InvalidArgument, "nil request data provided")
}
if req.Data.Target == nil {
return nil, status.Error(codes.InvalidArgument, "nil request data target provided")
}
if req.Data.Source == nil {
return nil, status.Error(codes.InvalidArgument, "nil request data source provided")
}
if req.Signature == nil {
return nil, status.Error(codes.InvalidArgument, "nil signature provided")
}

err := attestationutil.IsValidAttestationIndices(ctx, req)
if err != nil {
return nil, err
Expand Down Expand Up @@ -91,5 +108,47 @@ func (ss *Server) IsSlashableAttestation(ctx context.Context, req *ethpb.Indexed
// IsSlashableBlock returns an proposer slashing if the block submitted
// is a double proposal.
func (ss *Server) IsSlashableBlock(ctx context.Context, req *ethpb.SignedBeaconBlockHeader) (*slashpb.ProposerSlashingResponse, error) {
return nil, errors.New("unimplemented")
ctx, span := trace.StartSpan(ctx, "detection.IsSlashableBlock")
defer span.End()

if req == nil {
return nil, status.Error(codes.InvalidArgument, "nil request provided")
}
if req.Header == nil {
return nil, status.Error(codes.InvalidArgument, "nil header provided")

}
if req.Signature == nil {
return nil, status.Error(codes.InvalidArgument, "nil signature provided")
}
gvr, err := ss.beaconClient.GenesisValidatorsRoot(ctx)
shayzluf marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}
blockEpoch := helpers.SlotToEpoch(req.Header.Slot)
shayzluf marked this conversation as resolved.
Show resolved Hide resolved
fork, err := p2putils.Fork(blockEpoch)
if err != nil {
return nil, err
}
domain, err := helpers.Domain(fork, blockEpoch, params.BeaconConfig().DomainBeaconProposer, gvr)
if err != nil {
return nil, err
}
pkMap, err := ss.beaconClient.FindOrGetPublicKeys(ctx, []uint64{req.Header.ProposerIndex})
shayzluf marked this conversation as resolved.
Show resolved Hide resolved
if err := helpers.VerifyBlockHeaderSigningRoot(
req.Header, pkMap[req.Header.ProposerIndex], req.Signature, domain); err != nil {
return nil, err
}
slashing, err := ss.detector.DetectDoubleProposals(ctx, req)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not detect proposer slashing for block: %v: %v", req, err)
}
psr := &slashpb.ProposerSlashingResponse{}
if slashing != nil {
psr = &slashpb.ProposerSlashingResponse{
ProposerSlashing: []*ethpb.ProposerSlashing{slashing},
}
}
return psr, nil

}
109 changes: 106 additions & 3 deletions slasher/rpc/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
"github.com/golang/mock/gomock"
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil"
"github.com/prysmaticlabs/prysm/shared/bls"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
"github.com/prysmaticlabs/prysm/shared/mock"
"github.com/prysmaticlabs/prysm/shared/p2putils"
"github.com/prysmaticlabs/prysm/shared/params"
Expand All @@ -17,7 +19,7 @@ import (
"github.com/prysmaticlabs/prysm/slasher/detection"
)

func Test_DetectionFlow(t *testing.T) {
func TestServer_IsSlashableAttestation(t *testing.T) {
db := testDB.SetupSlasherDB(t, false)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Expand Down Expand Up @@ -94,7 +96,7 @@ func Test_DetectionFlow(t *testing.T) {

incomingAtt.Signature = marshalledSig

domain, err = helpers.Domain(fork, incomingAtt.Data.Target.Epoch, params.BeaconConfig().DomainBeaconAttester, wantedGenesis.GenesisValidatorsRoot)
domain, err = helpers.Domain(fork, savedAttestation.Data.Target.Epoch, params.BeaconConfig().DomainBeaconAttester, wantedGenesis.GenesisValidatorsRoot)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -127,7 +129,6 @@ func Test_DetectionFlow(t *testing.T) {
gomock.Any(),
gomock.Any(),
).Return(wantedValidators2, nil)
nClient.EXPECT().GetGenesis(gomock.Any(), gomock.Any()).Return(wantedGenesis, nil)
slashing, err := server.IsSlashableAttestation(ctx, incomingAtt)
if err != nil {
t.Fatalf("got error while trying to detect slashing: %v", err)
Expand All @@ -136,3 +137,105 @@ func Test_DetectionFlow(t *testing.T) {
t.Fatalf("only one slashing should have been found. got: %v", len(slashing.AttesterSlashing))
}
}

func TestServer_IsSlashableBlock(t *testing.T) {
db := testDB.SetupSlasherDB(t, false)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
bClient := mock.NewMockBeaconChainClient(ctrl)
nClient := mock.NewMockNodeClient(ctrl)
ctx := context.Background()

_, keys, err := testutil.DeterministicDepositsAndKeys(4)
if err != nil {
t.Fatal(err)
}
wantedValidators := &ethpb.Validators{
ValidatorList: []*ethpb.Validators_ValidatorContainer{
{
Index: 1, Validator: &ethpb.Validator{PublicKey: keys[1].PublicKey().Marshal()},
},
},
}
bClient.EXPECT().ListValidators(
gomock.Any(),
gomock.Any(),
).Return(wantedValidators, nil)

wantedGenesis := &ethpb.Genesis{
GenesisValidatorsRoot: []byte("I am genesis"),
}
nClient.EXPECT().GetGenesis(gomock.Any(), gomock.Any()).Return(wantedGenesis, nil)
savedBlock := &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 1,
ProposerIndex: 1,
BodyRoot: bytesutil.PadTo([]byte("body root"), 32),
},
}
incomingBlock := &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 1,
ProposerIndex: 1,
BodyRoot: bytesutil.PadTo([]byte("body root2"), 32),
},
}
cfg := &detection.Config{
SlasherDB: db,
}
incomingBlockEpoch := helpers.SlotToEpoch(incomingBlock.Header.Slot)
fork, err := p2putils.Fork(incomingBlockEpoch)
if err != nil {
t.Fatal(err)
}
domain, err := helpers.Domain(fork, incomingBlockEpoch, params.BeaconConfig().DomainBeaconProposer, wantedGenesis.GenesisValidatorsRoot)
if err != nil {
t.Fatal(err)
}
bhr, err := stateutil.BlockHeaderRoot(incomingBlock.Header)
if err != nil {
t.Error(err)
}
root, err := helpers.ComputeSigningRoot(bhr, domain)
if err != nil {
t.Error(err)
}
blockSig := keys[incomingBlock.Header.ProposerIndex].Sign(root[:])
marshalledSig := blockSig.Marshal()
incomingBlock.Signature = marshalledSig

savedBlockEpoch := helpers.SlotToEpoch(savedBlock.Header.Slot)
domain, err = helpers.Domain(fork, savedBlockEpoch, params.BeaconConfig().DomainBeaconProposer, wantedGenesis.GenesisValidatorsRoot)
if err != nil {
t.Fatal(err)
}
bhr, err = stateutil.BlockHeaderRoot(savedBlock.Header)
if err != nil {
t.Error(err)
}
root, err = helpers.ComputeSigningRoot(bhr, domain)
if err != nil {
t.Error(err)
}
blockSig = keys[savedBlock.Header.ProposerIndex].Sign(root[:])
marshalledSig = blockSig.Marshal()
savedBlock.Signature = marshalledSig
bcCfg := &beaconclient.Config{BeaconClient: bClient, NodeClient: nClient, SlasherDB: db}
bs, err := beaconclient.NewBeaconClientService(ctx, bcCfg)
ds := detection.NewDetectionService(ctx, cfg)
server := Server{ctx: ctx, detector: ds, slasherDB: db, beaconClient: bs}
slashings, err := server.IsSlashableBlock(ctx, savedBlock)
if err != nil {
t.Fatalf("got error while trying to detect slashing: %v", err)
}
if len(slashings.ProposerSlashing) != 0 {
t.Fatalf("Found slashings while no slashing should have been found on first block: %v slashing found: %v", savedBlock, slashings)
}
slashing, err := server.IsSlashableBlock(ctx, incomingBlock)
if err != nil {
t.Fatalf("got error while trying to detect slashing: %v", err)
}
if len(slashing.ProposerSlashing) != 1 {
t.Fatalf("only one slashing should have been found. got: %v", len(slashing.ProposerSlashing))
}
}