Implement SubmitVoluntaryExit and SubmitProposerSlashing in the beacon API (#8532)

* SubmitProposerSlashing

* SubmitVoluntaryExit

* rename variables
This commit is contained in:
Radosław Kapka
2021-03-01 17:08:39 +01:00
committed by GitHub
parent 9547f53e88
commit f973924fbf
6 changed files with 358 additions and 9 deletions

View File

@@ -30,8 +30,9 @@ func (m *PoolMock) InsertAttesterSlashing(_ context.Context, _ *state.BeaconStat
}
// InsertProposerSlashing --
func (m *PoolMock) InsertProposerSlashing(_ context.Context, _ *state.BeaconState, _ *ethpb.ProposerSlashing) error {
panic("implement me")
func (m *PoolMock) InsertProposerSlashing(_ context.Context, _ *state.BeaconState, slashing *ethpb.ProposerSlashing) error {
m.PendingPropSlashings = append(m.PendingPropSlashings, slashing)
return nil
}
// MarkIncludedAttesterSlashing --

View File

@@ -19,8 +19,8 @@ func (m *PoolMock) PendingExits(_ *beaconstate.BeaconState, _ types.Slot, _ bool
}
// InsertVoluntaryExit --
func (*PoolMock) InsertVoluntaryExit(_ context.Context, _ *beaconstate.BeaconState, _ *eth.SignedVoluntaryExit) {
panic("implement me")
func (m *PoolMock) InsertVoluntaryExit(_ context.Context, _ *beaconstate.BeaconState, exit *eth.SignedVoluntaryExit) {
m.Exits = append(m.Exits, exit)
}
// MarkIncluded --

View File

@@ -59,13 +59,13 @@ func (bs *Server) SubmitAttesterSlashing(ctx context.Context, req *ethpb.Atteste
return nil, status.Errorf(codes.Internal, "Could not get head state: %v", err)
}
v1Slashing := migration.V1AttSlashingToV1Alpha1(req)
err = blocks.VerifyAttesterSlashing(ctx, headState, v1Slashing)
alphaSlashing := migration.V1AttSlashingToV1Alpha1(req)
err = blocks.VerifyAttesterSlashing(ctx, headState, alphaSlashing)
if err != nil {
return nil, status.Errorf(codes.Internal, "Invalid attester slashing: %v", err)
}
err = bs.SlashingsPool.InsertAttesterSlashing(ctx, headState, v1Slashing)
err = bs.SlashingsPool.InsertAttesterSlashing(ctx, headState, alphaSlashing)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not insert attester slashing into pool: %v", err)
}
@@ -103,7 +103,31 @@ func (bs *Server) ListPoolProposerSlashings(ctx context.Context, req *ptypes.Emp
// SubmitProposerSlashing submits AttesterSlashing object to node's pool and if
// passes validation node MUST broadcast it to network.
func (bs *Server) SubmitProposerSlashing(ctx context.Context, req *ethpb.ProposerSlashing) (*ptypes.Empty, error) {
return nil, errors.New("unimplemented")
ctx, span := trace.StartSpan(ctx, "beaconv1.SubmitProposerSlashing")
defer span.End()
headState, err := bs.ChainInfoFetcher.HeadState(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get head state: %v", err)
}
alphaSlashing := migration.V1ProposerSlashingToV1Alpha1(req)
err = blocks.VerifyProposerSlashing(headState, alphaSlashing)
if err != nil {
return nil, status.Errorf(codes.Internal, "Invalid proposer slashing: %v", err)
}
err = bs.SlashingsPool.InsertProposerSlashing(ctx, headState, alphaSlashing)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not insert proposer slashing into pool: %v", err)
}
if !featureconfig.Get().DisableBroadcastSlashings {
if err := bs.Broadcaster.Broadcast(ctx, req); err != nil {
return nil, status.Errorf(codes.Internal, "Could not broadcast slashing object: %v", err)
}
}
return &ptypes.Empty{}, nil
}
// ListPoolVoluntaryExits retrieves voluntary exits known by the node but
@@ -132,5 +156,28 @@ func (bs *Server) ListPoolVoluntaryExits(ctx context.Context, req *ptypes.Empty)
// SubmitVoluntaryExit submits SignedVoluntaryExit object to node's pool
// and if passes validation node MUST broadcast it to network.
func (bs *Server) SubmitVoluntaryExit(ctx context.Context, req *ethpb.SignedVoluntaryExit) (*ptypes.Empty, error) {
return nil, errors.New("unimplemented")
ctx, span := trace.StartSpan(ctx, "beaconv1.SubmitVoluntaryExit")
defer span.End()
headState, err := bs.ChainInfoFetcher.HeadState(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get head state: %v", err)
}
validator, err := headState.ValidatorAtIndexReadOnly(req.Exit.ValidatorIndex)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get exiting validator: %v", err)
}
alphaExit := migration.V1ExitToV1Alpha1(req)
err = blocks.VerifyExitAndSignature(validator, headState.Slot(), headState.Fork(), alphaExit, headState.GenesisValidatorRoot())
if err != nil {
return nil, status.Errorf(codes.Internal, "Invalid voluntary exit: %v", err)
}
bs.VoluntaryExitsPool.InsertVoluntaryExit(ctx, headState, alphaExit)
if err := bs.Broadcaster.Broadcast(ctx, req); err != nil {
return nil, status.Errorf(codes.Internal, "Could not broadcast voluntary exit object: %v", err)
}
return &ptypes.Empty{}, nil
}

View File

@@ -5,6 +5,7 @@ import (
"testing"
"github.com/gogo/protobuf/types"
eth2types "github.com/prysmaticlabs/eth2-types"
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1"
eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
chainMock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
@@ -314,3 +315,220 @@ func TestSubmitAttesterSlashing_InvalidSlashing(t *testing.T) {
require.ErrorContains(t, "Invalid attester slashing", err)
assert.Equal(t, false, broadcaster.BroadcastCalled)
}
func TestSubmitProposerSlashing_Ok(t *testing.T) {
ctx := context.Background()
_, keys, err := testutil.DeterministicDepositsAndKeys(1)
require.NoError(t, err)
validator := &eth.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
PublicKey: keys[0].PublicKey().Marshal(),
WithdrawalCredentials: make([]byte, 32),
WithdrawableEpoch: eth2types.Epoch(1),
}
state, err := testutil.NewBeaconState(func(state *pb.BeaconState) {
state.Validators = []*eth.Validator{validator}
})
require.NoError(t, err)
slashing := &ethpb.ProposerSlashing{
Header_1: &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 1,
ProposerIndex: 0,
ParentRoot: bytesutil.PadTo([]byte("parentroot1"), 32),
StateRoot: bytesutil.PadTo([]byte("stateroot1"), 32),
BodyRoot: bytesutil.PadTo([]byte("bodyroot1"), 32),
},
Signature: make([]byte, 96),
},
Header_2: &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 1,
ProposerIndex: 0,
ParentRoot: bytesutil.PadTo([]byte("parentroot2"), 32),
StateRoot: bytesutil.PadTo([]byte("stateroot2"), 32),
BodyRoot: bytesutil.PadTo([]byte("bodyroot2"), 32),
},
Signature: make([]byte, 96),
},
}
for _, h := range []*ethpb.SignedBeaconBlockHeader{slashing.Header_1, slashing.Header_2} {
sb, err := helpers.ComputeDomainAndSign(
state,
helpers.SlotToEpoch(h.Header.Slot),
h.Header,
params.BeaconConfig().DomainBeaconProposer,
keys[0],
)
require.NoError(t, err)
sig, err := bls.SignatureFromBytes(sb)
require.NoError(t, err)
h.Signature = sig.Marshal()
}
broadcaster := &p2pMock.MockBroadcaster{}
s := &Server{
ChainInfoFetcher: &chainMock.ChainService{State: state},
SlashingsPool: &slashings.PoolMock{},
Broadcaster: broadcaster,
}
_, err = s.SubmitProposerSlashing(ctx, slashing)
require.NoError(t, err)
pendingSlashings := s.SlashingsPool.PendingProposerSlashings(ctx, state, true)
require.Equal(t, 1, len(pendingSlashings))
assert.DeepEqual(t, migration.V1ProposerSlashingToV1Alpha1(slashing), pendingSlashings[0])
assert.Equal(t, true, broadcaster.BroadcastCalled)
}
func TestSubmitProposerSlashing_InvalidSlashing(t *testing.T) {
ctx := context.Background()
state, err := testutil.NewBeaconState()
require.NoError(t, err)
header := &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 1,
ProposerIndex: 0,
ParentRoot: bytesutil.PadTo([]byte("parentroot1"), 32),
StateRoot: bytesutil.PadTo([]byte("stateroot1"), 32),
BodyRoot: bytesutil.PadTo([]byte("bodyroot1"), 32),
},
Signature: make([]byte, 96),
}
slashing := &ethpb.ProposerSlashing{
Header_1: header,
Header_2: header,
}
broadcaster := &p2pMock.MockBroadcaster{}
s := &Server{
ChainInfoFetcher: &chainMock.ChainService{State: state},
SlashingsPool: &slashings.PoolMock{},
Broadcaster: broadcaster,
}
_, err = s.SubmitProposerSlashing(ctx, slashing)
require.ErrorContains(t, "Invalid proposer slashing", err)
assert.Equal(t, false, broadcaster.BroadcastCalled)
}
func TestSubmitVoluntaryExit_Ok(t *testing.T) {
ctx := context.Background()
_, keys, err := testutil.DeterministicDepositsAndKeys(1)
require.NoError(t, err)
validator := &eth.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
PublicKey: keys[0].PublicKey().Marshal(),
WithdrawalCredentials: make([]byte, 32),
}
state, err := testutil.NewBeaconState(func(state *pb.BeaconState) {
state.Validators = []*eth.Validator{validator}
// Satisfy activity time required before exiting.
state.Slot = params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().ShardCommitteePeriod))
})
require.NoError(t, err)
exit := &ethpb.SignedVoluntaryExit{
Exit: &ethpb.VoluntaryExit{
Epoch: 0,
ValidatorIndex: 0,
},
Signature: make([]byte, 96),
}
sb, err := helpers.ComputeDomainAndSign(state, exit.Exit.Epoch, exit.Exit, params.BeaconConfig().DomainVoluntaryExit, keys[0])
require.NoError(t, err)
sig, err := bls.SignatureFromBytes(sb)
require.NoError(t, err)
exit.Signature = sig.Marshal()
broadcaster := &p2pMock.MockBroadcaster{}
s := &Server{
ChainInfoFetcher: &chainMock.ChainService{State: state},
VoluntaryExitsPool: &voluntaryexits.PoolMock{},
Broadcaster: broadcaster,
}
_, err = s.SubmitVoluntaryExit(ctx, exit)
require.NoError(t, err)
pendingExits := s.VoluntaryExitsPool.PendingExits(state, state.Slot(), true)
require.Equal(t, 1, len(pendingExits))
assert.DeepEqual(t, migration.V1ExitToV1Alpha1(exit), pendingExits[0])
assert.Equal(t, true, broadcaster.BroadcastCalled)
}
func TestSubmitVoluntaryExit_InvalidValidatorIndex(t *testing.T) {
ctx := context.Background()
_, keys, err := testutil.DeterministicDepositsAndKeys(1)
require.NoError(t, err)
validator := &eth.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
PublicKey: keys[0].PublicKey().Marshal(),
WithdrawalCredentials: make([]byte, 32),
}
state, err := testutil.NewBeaconState(func(state *pb.BeaconState) {
state.Validators = []*eth.Validator{validator}
})
require.NoError(t, err)
exit := &ethpb.SignedVoluntaryExit{
Exit: &ethpb.VoluntaryExit{
Epoch: 0,
ValidatorIndex: 99,
},
Signature: make([]byte, 96),
}
broadcaster := &p2pMock.MockBroadcaster{}
s := &Server{
ChainInfoFetcher: &chainMock.ChainService{State: state},
VoluntaryExitsPool: &voluntaryexits.PoolMock{},
Broadcaster: broadcaster,
}
_, err = s.SubmitVoluntaryExit(ctx, exit)
require.ErrorContains(t, "Could not get exiting validator", err)
assert.Equal(t, false, broadcaster.BroadcastCalled)
}
func TestSubmitVoluntaryExit_InvalidExit(t *testing.T) {
ctx := context.Background()
_, keys, err := testutil.DeterministicDepositsAndKeys(1)
require.NoError(t, err)
validator := &eth.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
PublicKey: keys[0].PublicKey().Marshal(),
WithdrawalCredentials: make([]byte, 32),
}
state, err := testutil.NewBeaconState(func(state *pb.BeaconState) {
state.Validators = []*eth.Validator{validator}
})
require.NoError(t, err)
exit := &ethpb.SignedVoluntaryExit{
Exit: &ethpb.VoluntaryExit{
Epoch: 0,
ValidatorIndex: 0,
},
Signature: make([]byte, 96),
}
broadcaster := &p2pMock.MockBroadcaster{}
s := &Server{
ChainInfoFetcher: &chainMock.ChainService{State: state},
VoluntaryExitsPool: &voluntaryexits.PoolMock{},
Broadcaster: broadcaster,
}
_, err = s.SubmitVoluntaryExit(ctx, exit)
require.ErrorContains(t, "Invalid voluntary exit", err)
assert.Equal(t, false, broadcaster.BroadcastCalled)
}

View File

@@ -111,6 +111,23 @@ func V1Alpha1SignedHeaderToV1(v1alpha1Hdr *ethpb_alpha.SignedBeaconBlockHeader)
}
}
// V1SignedHeaderToV1Alpha1 converts a v1 signed beacon block header to v1alpha1.
func V1SignedHeaderToV1Alpha1(v1Header *ethpb.SignedBeaconBlockHeader) *ethpb_alpha.SignedBeaconBlockHeader {
if v1Header == nil || v1Header.Header == nil {
return &ethpb_alpha.SignedBeaconBlockHeader{}
}
return &ethpb_alpha.SignedBeaconBlockHeader{
Header: &ethpb_alpha.BeaconBlockHeader{
Slot: v1Header.Header.Slot,
ProposerIndex: v1Header.Header.ProposerIndex,
ParentRoot: v1Header.Header.ParentRoot,
StateRoot: v1Header.Header.StateRoot,
BodyRoot: v1Header.Header.BodyRoot,
},
Signature: v1Header.Signature,
}
}
// V1Alpha1ProposerSlashingToV1 converts a v1alpha1 proposer slashing to v1.
func V1Alpha1ProposerSlashingToV1(v1alpha1Slashing *ethpb_alpha.ProposerSlashing) *ethpb.ProposerSlashing {
if v1alpha1Slashing == nil {
@@ -136,6 +153,20 @@ func V1Alpha1ExitToV1(v1alpha1Exit *ethpb_alpha.SignedVoluntaryExit) *ethpb.Sign
}
}
// V1ExitToV1Alpha1 converts a v1 SignedVoluntaryExit to v1alpha1.
func V1ExitToV1Alpha1(v1Exit *ethpb.SignedVoluntaryExit) *ethpb_alpha.SignedVoluntaryExit {
if v1Exit == nil || v1Exit.Exit == nil {
return &ethpb_alpha.SignedVoluntaryExit{}
}
return &ethpb_alpha.SignedVoluntaryExit{
Exit: &ethpb_alpha.VoluntaryExit{
Epoch: v1Exit.Exit.Epoch,
ValidatorIndex: v1Exit.Exit.ValidatorIndex,
},
Signature: v1Exit.Signature,
}
}
// V1IndexedAttToV1Alpha1 converts a v1 indexed attestation to v1alpha1.
func V1IndexedAttToV1Alpha1(v1Att *ethpb.IndexedAttestation) *ethpb_alpha.IndexedAttestation {
if v1Att == nil {
@@ -178,3 +209,14 @@ func V1AttSlashingToV1Alpha1(v1Slashing *ethpb.AttesterSlashing) *ethpb_alpha.At
Attestation_2: V1IndexedAttToV1Alpha1(v1Slashing.Attestation_2),
}
}
// V1ProposerSlashingToV1Alpha1 converts a v1 proposer slashing to v1alpha1.
func V1ProposerSlashingToV1Alpha1(v1Slashing *ethpb.ProposerSlashing) *ethpb_alpha.ProposerSlashing {
if v1Slashing == nil {
return &ethpb_alpha.ProposerSlashing{}
}
return &ethpb_alpha.ProposerSlashing{
Header_1: V1SignedHeaderToV1Alpha1(v1Slashing.Header_1),
Header_2: V1SignedHeaderToV1Alpha1(v1Slashing.Header_2),
}
}

View File

@@ -166,6 +166,23 @@ func Test_V1Alpha1ExitToV1(t *testing.T) {
assert.DeepEqual(t, alphaRoot, v1Root)
}
func Test_V1ExitToV1Alpha1(t *testing.T) {
v1Exit := &ethpb.SignedVoluntaryExit{
Exit: &ethpb.VoluntaryExit{
Epoch: epoch,
ValidatorIndex: validatorIndex,
},
Signature: signature,
}
alphaExit := V1ExitToV1Alpha1(v1Exit)
alphaRoot, err := alphaExit.HashTreeRoot()
require.NoError(t, err)
v1Root, err := v1Exit.HashTreeRoot()
require.NoError(t, err)
assert.DeepEqual(t, alphaRoot, v1Root)
}
func Test_V1AttSlashingToV1Alpha1(t *testing.T) {
v1Attestation := &ethpb.IndexedAttestation{
AttestingIndices: attestingIndices,
@@ -196,3 +213,27 @@ func Test_V1AttSlashingToV1Alpha1(t *testing.T) {
require.NoError(t, err)
assert.DeepEqual(t, v1Root, alphaRoot)
}
func Test_V1ProposerSlashingToV1Alpha1(t *testing.T) {
v1Header := &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: slot,
ProposerIndex: validatorIndex,
ParentRoot: parentRoot,
StateRoot: stateRoot,
BodyRoot: bodyRoot,
},
Signature: signature,
}
v1Slashing := &ethpb.ProposerSlashing{
Header_1: v1Header,
Header_2: v1Header,
}
alphaSlashing := V1ProposerSlashingToV1Alpha1(v1Slashing)
alphaRoot, err := alphaSlashing.HashTreeRoot()
require.NoError(t, err)
v1Root, err := v1Slashing.HashTreeRoot()
require.NoError(t, err)
assert.DeepEqual(t, alphaRoot, v1Root)
}