Skip to content

Commit

Permalink
malfeasance2: update GRPC API (#6557)
Browse files Browse the repository at this point in the history
## Motivation

This updates the malfeasance v2alpha1 endpoints to be able to return both legacy and v2 malfeasance proofs.
  • Loading branch information
fasmat committed Dec 19, 2024
1 parent febfe27 commit 3eb9a83
Show file tree
Hide file tree
Showing 24 changed files with 972 additions and 367 deletions.
36 changes: 33 additions & 3 deletions api/grpcserver/activation_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import (
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"

"github.com/spacemeshos/go-spacemesh/codec"
"github.com/spacemeshos/go-spacemesh/common/types"
"github.com/spacemeshos/go-spacemesh/events"
"github.com/spacemeshos/go-spacemesh/malfeasance/wire"
"github.com/spacemeshos/go-spacemesh/sql"
)

Expand Down Expand Up @@ -75,7 +76,6 @@ func (s *activationService) Get(ctx context.Context, request *pb.GetRequest) (*p
proof, err := s.atxProvider.MalfeasanceProof(atx.SmesherID)
if err != nil && !errors.Is(err, sql.ErrNotFound) {
ctxzap.Error(ctx, "failed to get malfeasance proof",
zap.Stringer("smesher", atx.SmesherID),
zap.Stringer("smesher", atx.SmesherID),
zap.Stringer("id", atxId),
zap.Error(err),
Expand All @@ -86,7 +86,7 @@ func (s *activationService) Get(ctx context.Context, request *pb.GetRequest) (*p
Atx: convertActivation(atx, prev),
}
if proof != nil {
resp.MalfeasanceProof = events.ToMalfeasancePB(atx.SmesherID, proof, false)
resp.MalfeasanceProof = toMalfeasancePB(atx.SmesherID, proof, false)
}
return resp, nil
}
Expand Down Expand Up @@ -117,3 +117,33 @@ func (s *activationService) Highest(ctx context.Context, req *emptypb.Empty) (*p
Atx: convertActivation(atx, prev),
}, nil
}

func toMalfeasancePB(nodeID types.NodeID, proof []byte, includeProof bool) *pb.MalfeasanceProof {
mp := &wire.MalfeasanceProof{}
if err := codec.Decode(proof, mp); err != nil {
return &pb.MalfeasanceProof{}
}
kind := pb.MalfeasanceProof_MALFEASANCE_UNSPECIFIED
switch mp.Proof.Type {
case wire.MultipleATXs:
kind = pb.MalfeasanceProof_MALFEASANCE_ATX
case wire.MultipleBallots:
kind = pb.MalfeasanceProof_MALFEASANCE_BALLOT
case wire.HareEquivocation:
kind = pb.MalfeasanceProof_MALFEASANCE_HARE
case wire.InvalidPostIndex:
kind = pb.MalfeasanceProof_MALFEASANCE_POST_INDEX
case wire.InvalidPrevATX:
kind = pb.MalfeasanceProof_MALFEASANCE_INCORRECT_PREV_ATX
}
result := &pb.MalfeasanceProof{
SmesherId: &pb.SmesherId{Id: nodeID.Bytes()},
Layer: &pb.LayerNumber{Number: mp.Layer.Uint32()},
Kind: kind,
DebugInfo: wire.MalfeasanceInfo(nodeID, mp),
}
if includeProof {
result.Proof = proof
}
return result
}
40 changes: 19 additions & 21 deletions api/grpcserver/activation_service_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package grpcserver_test
package grpcserver

import (
"context"
Expand All @@ -13,19 +13,17 @@ import (
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"

"github.com/spacemeshos/go-spacemesh/api/grpcserver"
"github.com/spacemeshos/go-spacemesh/codec"
"github.com/spacemeshos/go-spacemesh/common/types"
"github.com/spacemeshos/go-spacemesh/events"
"github.com/spacemeshos/go-spacemesh/sql"
"github.com/spacemeshos/go-spacemesh/sql/statesql"
)

func Test_Highest_ReturnsGoldenAtxOnError(t *testing.T) {
ctrl := gomock.NewController(t)
atxProvider := grpcserver.NewMockatxProvider(ctrl)
atxProvider := NewMockatxProvider(ctrl)
goldenAtx := types.ATXID{2, 3, 4}
activationService := grpcserver.NewActivationService(atxProvider, goldenAtx)
activationService := NewActivationService(atxProvider, goldenAtx)

atxProvider.EXPECT().MaxHeightAtx().Return(types.EmptyATXID, errors.New("blah"))
response, err := activationService.Highest(context.Background(), &emptypb.Empty{})
Expand All @@ -41,9 +39,9 @@ func Test_Highest_ReturnsGoldenAtxOnError(t *testing.T) {

func Test_Highest_ReturnsMaxTickHeight(t *testing.T) {
ctrl := gomock.NewController(t)
atxProvider := grpcserver.NewMockatxProvider(ctrl)
atxProvider := NewMockatxProvider(ctrl)
goldenAtx := types.ATXID{2, 3, 4}
activationService := grpcserver.NewActivationService(atxProvider, goldenAtx)
activationService := NewActivationService(atxProvider, goldenAtx)

previous := types.RandomATXID()
atx := types.ActivationTx{
Expand Down Expand Up @@ -71,8 +69,8 @@ func Test_Highest_ReturnsMaxTickHeight(t *testing.T) {

func TestGet_RejectInvalidAtxID(t *testing.T) {
ctrl := gomock.NewController(t)
atxProvider := grpcserver.NewMockatxProvider(ctrl)
activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1})
atxProvider := NewMockatxProvider(ctrl)
activationService := NewActivationService(atxProvider, types.ATXID{1})

_, err := activationService.Get(context.Background(), &pb.GetRequest{Id: []byte{1, 2, 3}})
require.Error(t, err)
Expand All @@ -81,8 +79,8 @@ func TestGet_RejectInvalidAtxID(t *testing.T) {

func TestGet_AtxNotPresent(t *testing.T) {
ctrl := gomock.NewController(t)
atxProvider := grpcserver.NewMockatxProvider(ctrl)
activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1})
atxProvider := NewMockatxProvider(ctrl)
activationService := NewActivationService(atxProvider, types.ATXID{1})

id := types.RandomATXID()
atxProvider.EXPECT().GetAtx(id).Return(nil, nil)
Expand All @@ -94,8 +92,8 @@ func TestGet_AtxNotPresent(t *testing.T) {

func TestGet_AtxProviderReturnsFailure(t *testing.T) {
ctrl := gomock.NewController(t)
atxProvider := grpcserver.NewMockatxProvider(ctrl)
activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1})
atxProvider := NewMockatxProvider(ctrl)
activationService := NewActivationService(atxProvider, types.ATXID{1})

id := types.RandomATXID()
atxProvider.EXPECT().GetAtx(id).Return(&types.ActivationTx{}, errors.New(""))
Expand All @@ -107,8 +105,8 @@ func TestGet_AtxProviderReturnsFailure(t *testing.T) {

func TestGet_AtxProviderFailsObtainPreviousAtxs(t *testing.T) {
ctrl := gomock.NewController(t)
atxProvider := grpcserver.NewMockatxProvider(ctrl)
activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1})
atxProvider := NewMockatxProvider(ctrl)
activationService := NewActivationService(atxProvider, types.ATXID{1})

id := types.RandomATXID()
atxProvider.EXPECT().GetAtx(id).Return(&types.ActivationTx{}, nil)
Expand All @@ -121,8 +119,8 @@ func TestGet_AtxProviderFailsObtainPreviousAtxs(t *testing.T) {

func TestGet_HappyPath(t *testing.T) {
ctrl := gomock.NewController(t)
atxProvider := grpcserver.NewMockatxProvider(ctrl)
activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1})
atxProvider := NewMockatxProvider(ctrl)
activationService := NewActivationService(atxProvider, types.ATXID{1})

previous := []types.ATXID{types.RandomATXID(), types.RandomATXID()}
id := types.RandomATXID()
Expand Down Expand Up @@ -155,10 +153,10 @@ func TestGet_HappyPath(t *testing.T) {

func TestGet_IdentityCanceled(t *testing.T) {
ctrl := gomock.NewController(t)
atxProvider := grpcserver.NewMockatxProvider(ctrl)
activationService := grpcserver.NewActivationService(atxProvider, types.ATXID{1})
atxProvider := NewMockatxProvider(ctrl)
activationService := NewActivationService(atxProvider, types.ATXID{1})

smesher, proof := grpcserver.BallotMalfeasance(t, statesql.InMemoryTest(t))
smesher, proof := BallotMalfeasance(t, statesql.InMemoryTest(t))
previous := types.RandomATXID()
id := types.RandomATXID()
atx := types.ActivationTx{
Expand All @@ -185,5 +183,5 @@ func TestGet_IdentityCanceled(t *testing.T) {
require.Equal(t, previous.Bytes(), response.Atx.PreviousAtxs[0].Id)
require.Equal(t, atx.NumUnits, response.Atx.NumUnits)
require.Equal(t, atx.Sequence, response.Atx.Sequence)
require.Equal(t, events.ToMalfeasancePB(smesher, codec.MustEncode(proof), false), response.MalfeasanceProof)
require.Equal(t, toMalfeasancePB(smesher, codec.MustEncode(proof), false), response.MalfeasanceProof)
}
2 changes: 1 addition & 1 deletion api/grpcserver/debug_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ func castEventProposal(ev *events.EventProposal) *pb.Proposal {
for _, el := range ev.Proposal.Ballot.EligibilityProofs {
proposal.Eligibilities = append(proposal.Eligibilities, &pb.Eligibility{
J: el.J,
Signature: el.Sig[:],
Signature: el.Sig.Bytes(),
})
}
return proposal
Expand Down
19 changes: 13 additions & 6 deletions api/grpcserver/mesh_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ func (s *MeshService) MalfeasanceQuery(
return nil, status.Error(codes.Internal, err.Error())
}
return &pb.MalfeasanceResponse{
Proof: events.ToMalfeasancePB(id, proof, req.IncludeProof),
Proof: toMalfeasancePB(id, proof, req.IncludeProof),
}, nil
}

Expand All @@ -627,7 +627,7 @@ func (s *MeshService) MalfeasanceStream(
if sub == nil {
return status.Errorf(codes.FailedPrecondition, "event reporting is not enabled")
}
eventch, fullch := consumeEvents[events.EventMalfeasance](stream.Context(), sub)
eventCh, fullCh := consumeEvents[events.EventMalfeasance](stream.Context(), sub)
if err := stream.SendHeader(metadata.MD{}); err != nil {
return status.Errorf(codes.Unavailable, "can't send header")
}
Expand All @@ -639,7 +639,7 @@ func (s *MeshService) MalfeasanceStream(
return nil
default:
res := &pb.MalfeasanceStreamResponse{
Proof: events.ToMalfeasancePB(id, proof, req.IncludeProof),
Proof: toMalfeasancePB(id, proof, req.IncludeProof),
}
return stream.Send(res)
}
Expand All @@ -651,11 +651,18 @@ func (s *MeshService) MalfeasanceStream(
select {
case <-stream.Context().Done():
return nil
case <-fullch:
case <-fullCh:
return status.Errorf(codes.Canceled, "buffer is full")
case ev := <-eventch:
case ev := <-eventCh:
proof, err := s.cdb.MalfeasanceProof(ev.Smesher)
if err != nil {
return status.Error(
codes.Internal,
fmt.Errorf("load malfeasance proof for %s: %w", ev.Smesher.ShortString(), err).Error(),
)
}
if err := stream.Send(&pb.MalfeasanceStreamResponse{
Proof: events.ToMalfeasancePB(ev.Smesher, ev.Proof, req.IncludeProof),
Proof: toMalfeasancePB(ev.Smesher, proof, req.IncludeProof),
}); err != nil {
return status.Error(codes.Internal, fmt.Errorf("send to stream: %w", err).Error())
}
Expand Down
10 changes: 5 additions & 5 deletions api/grpcserver/mesh_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func TestMeshService_MalfeasanceQuery(t *testing.T) {
require.Equal(t, nodeID, types.BytesToNodeID(resp.Proof.SmesherId.Id))
require.EqualValues(t, layer, resp.Proof.Layer.Number)
require.Equal(t, pb.MalfeasanceProof_MALFEASANCE_BALLOT, resp.Proof.Kind)
require.Equal(t, events.ToMalfeasancePB(nodeID, codec.MustEncode(proof), true), resp.Proof)
require.Equal(t, toMalfeasancePB(nodeID, codec.MustEncode(proof), true), resp.Proof)
require.NotEmpty(t, resp.Proof.Proof)
var got wire.MalfeasanceProof
require.NoError(t, codec.Decode(resp.Proof.Proof, &got))
Expand Down Expand Up @@ -251,16 +251,16 @@ func TestMeshService_MalfeasanceStream(t *testing.T) {

id, proof := AtxMalfeasance(t, db)
proofBytes := codec.MustEncode(proof)
events.ReportMalfeasance(id, proofBytes)
events.ReportMalfeasance(id)
resp, err := stream.Recv()
require.NoError(t, err)
require.Equal(t, events.ToMalfeasancePB(id, proofBytes, false), resp.Proof)
require.Equal(t, toMalfeasancePB(id, proofBytes, false), resp.Proof)
id, proof = BallotMalfeasance(t, db)
proofBytes = codec.MustEncode(proof)
events.ReportMalfeasance(id, proofBytes)
events.ReportMalfeasance(id)
resp, err = stream.Recv()
require.NoError(t, err)
require.Equal(t, events.ToMalfeasancePB(id, proofBytes, false), resp.Proof)
require.Equal(t, toMalfeasancePB(id, proofBytes, false), resp.Proof)
}

type MeshAPIMockInstrumented struct {
Expand Down
8 changes: 7 additions & 1 deletion api/grpcserver/v2alpha1/interface.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package v2alpha1

import (
"context"

"github.com/spacemeshos/go-spacemesh/common/types"
)

//go:generate mockgen -typed -package=v2alpha1 -destination=./mocks.go -source=./interface.go

type malfeasanceInfo interface {
Info(data []byte) (map[string]string, error)
Info(ctx context.Context, nodeID types.NodeID) (map[string]string, error)
}
Loading

0 comments on commit 3eb9a83

Please sign in to comment.