Skip to content

Commit

Permalink
Is slashable block rpc endpoint implementation (#5765)
Browse files Browse the repository at this point in the history
* is slashable block rpc endpoint implementation

* add span

* raul feedback

* fix error

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
  • Loading branch information
shayzluf and prylabs-bulldozer[bot] authored May 8, 2020
1 parent 63e2e8a commit c1a8b41
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 19 deletions.
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)
if err != nil {
return nil, err
}
blockEpoch := helpers.SlotToEpoch(req.Header.Slot)
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})
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))
}
}

0 comments on commit c1a8b41

Please sign in to comment.