Skip to content
This repository has been archived by the owner on May 23, 2024. It is now read-only.

Commit

Permalink
Return 'Reject' on invalid malf proofs. (#5796)
Browse files Browse the repository at this point in the history
## Motivation

The malfeasance proofs handler should return `pubsub.ErrValidationReject` on invalid proofs to disconnect the peer that sends such broken proofs.
  • Loading branch information
poszu committed Apr 3, 2024
1 parent df50e60 commit 455f0de
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 80 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

See [RELEASE](./RELEASE.md) for workflow instructions.

## UNRELEASED

### Highlights

### Improvements

* [#5796](https://github.com/spacemeshos/go-spacemesh/pull/5796) Reject p2p messages containing invalid malfeasance proofs.

### Features

## Release v1.4.4

### Improvements
Expand Down
2 changes: 1 addition & 1 deletion common/types/malfeasance.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func (e *Proof) DecodeScale(dec *scale.Decoder) (int, error) {

type MalfeasanceGossip struct {
MalfeasanceProof
Eligibility *HareEligibilityGossip // optional, only useful in live hare rounds
Eligibility *HareEligibilityGossip // deprecated - to be removed in the next version
}

func (mg *MalfeasanceGossip) MarshalLogObject(encoder log.ObjectEncoder) error {
Expand Down
17 changes: 8 additions & 9 deletions malfeasance/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var (
ErrKnownProof = errors.New("known proof")
errMalformedData = fmt.Errorf("%w: malformed data", pubsub.ErrValidationReject)
errWrongHash = fmt.Errorf("%w: incorrect hash", pubsub.ErrValidationReject)
errInvalidProof = fmt.Errorf("%w: invalid proof", pubsub.ErrValidationReject)
)

// Handler processes MalfeasanceProof from gossip and, if deems it valid, propagates it to peers.
Expand Down Expand Up @@ -186,19 +187,17 @@ func Validate(
proof := p.MalfeasanceProof.Proof.Data.(*types.InvalidPostIndexProof) // guaranteed to work by scale func
nodeID, err = validateInvalidPostIndex(ctx, logger, cdb, edVerifier, postVerifier, proof)
default:
return nodeID, errors.New("unknown malfeasance type")
return nodeID, fmt.Errorf("%w: unknown malfeasance type", errInvalidProof)
}

if err != nil {
if !errors.Is(err, ErrKnownProof) {
logger.WithContext(ctx).With().Warning("failed to validate malfeasance proof",
log.Inline(p),
log.Err(err),
)
}
switch {
case err == nil:
return nodeID, nil
case errors.Is(err, ErrKnownProof):
return nodeID, err
}
return nodeID, nil
logger.WithContext(ctx).With().Warning("malfeasance proof failed validation", log.Inline(p), log.Err(err))
return nodeID, fmt.Errorf("%w: %v", errInvalidProof, err)
}

func updateMetrics(tp types.Proof) {
Expand Down
112 changes: 42 additions & 70 deletions malfeasance/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,9 @@ func TestHandler_HandleMalfeasanceProof_hareEquivocation(t *testing.T) {
t.Run("unknown identity", func(t *testing.T) {
hp := hareProof
hp.Messages[0].Signature = sig.Sign(signing.HARE, hp.Messages[0].SignedBytes())
hp.Messages[0].SmesherID = sig.NodeID()
hp.Messages[1].Signature = sig.Sign(signing.HARE, hp.Messages[1].SignedBytes())
hp.Messages[1].SmesherID = sig.NodeID()
gossip := &types.MalfeasanceGossip{
MalfeasanceProof: types.MalfeasanceProof{
Layer: lid,
Expand All @@ -534,7 +536,7 @@ func TestHandler_HandleMalfeasanceProof_hareEquivocation(t *testing.T) {
}
data, err := codec.Encode(gossip)
require.NoError(t, err)
require.Error(t, h.HandleMalfeasanceProof(context.Background(), "peer", data))
require.ErrorIs(t, h.HandleMalfeasanceProof(context.Background(), "peer", data), pubsub.ErrValidationReject)

malProof, err := identities.GetMalfeasanceProof(db, sig.NodeID())
require.ErrorIs(t, err, sql.ErrNotFound)
Expand All @@ -543,13 +545,40 @@ func TestHandler_HandleMalfeasanceProof_hareEquivocation(t *testing.T) {

createIdentity(t, db, sig)

t.Run("invalid signature", func(t *testing.T) {
hp := hareProof
hp.Messages[0].Signature = sig.Sign(signing.HARE, hp.Messages[0].SignedBytes())
hp.Messages[0].SmesherID = sig.NodeID()
hp.Messages[1].Signature = types.RandomEdSignature()
hp.Messages[1].SmesherID = sig.NodeID()
gossip := &types.MalfeasanceGossip{
MalfeasanceProof: types.MalfeasanceProof{
Layer: lid,
Proof: types.Proof{
Type: types.HareEquivocation,
Data: &hp,
},
},
}
data, err := codec.Encode(gossip)
require.NoError(t, err)
require.ErrorIs(t, h.HandleMalfeasanceProof(context.Background(), "peer", data), pubsub.ErrValidationReject)

malProof, err := identities.GetMalfeasanceProof(db, sig.NodeID())
require.ErrorIs(t, err, sql.ErrNotFound)
require.Nil(t, malProof)
})

t.Run("same msg hash", func(t *testing.T) {
msgHash := types.RandomHash()
hp := hareProof
hp.Messages[0].InnerMsg.MsgHash = msgHash
hp.Messages[1].InnerMsg.MsgHash = msgHash
hp.Messages[0].Signature = sig.Sign(signing.HARE, hp.Messages[0].SignedBytes())
hp.Messages[0].SmesherID = sig.NodeID()
hp.Messages[1].Signature = sig.Sign(signing.HARE, hp.Messages[1].SignedBytes())
hp.Messages[1].SmesherID = sig.NodeID()

gossip := &types.MalfeasanceGossip{
MalfeasanceProof: types.MalfeasanceProof{
Layer: lid,
Expand All @@ -561,7 +590,7 @@ func TestHandler_HandleMalfeasanceProof_hareEquivocation(t *testing.T) {
}
data, err := codec.Encode(gossip)
require.NoError(t, err)
require.Error(t, h.HandleMalfeasanceProof(context.Background(), "peer", data))
require.ErrorIs(t, h.HandleMalfeasanceProof(context.Background(), "peer", data), pubsub.ErrValidationReject)

malProof, err := identities.GetMalfeasanceProof(db, sig.NodeID())
require.ErrorIs(t, err, sql.ErrNotFound)
Expand All @@ -572,7 +601,9 @@ func TestHandler_HandleMalfeasanceProof_hareEquivocation(t *testing.T) {
hp := hareProof
hp.Messages[1].InnerMsg.Layer = lid.Sub(1)
hp.Messages[0].Signature = sig.Sign(signing.HARE, hp.Messages[0].SignedBytes())
hp.Messages[0].SmesherID = sig.NodeID()
hp.Messages[1].Signature = sig.Sign(signing.HARE, hp.Messages[1].SignedBytes())
hp.Messages[1].SmesherID = sig.NodeID()
gossip := &types.MalfeasanceGossip{
MalfeasanceProof: types.MalfeasanceProof{
Layer: lid,
Expand All @@ -584,7 +615,7 @@ func TestHandler_HandleMalfeasanceProof_hareEquivocation(t *testing.T) {
}
data, err := codec.Encode(gossip)
require.NoError(t, err)
require.Error(t, h.HandleMalfeasanceProof(context.Background(), "peer", data))
require.ErrorIs(t, h.HandleMalfeasanceProof(context.Background(), "peer", data), pubsub.ErrValidationReject)

malProof, err := identities.GetMalfeasanceProof(db, sig.NodeID())
require.ErrorIs(t, err, sql.ErrNotFound)
Expand All @@ -595,7 +626,9 @@ func TestHandler_HandleMalfeasanceProof_hareEquivocation(t *testing.T) {
hp := hareProof
hp.Messages[0].InnerMsg.Round = hp.Messages[1].InnerMsg.Round + 1
hp.Messages[0].Signature = sig.Sign(signing.HARE, hp.Messages[0].SignedBytes())
hp.Messages[0].SmesherID = sig.NodeID()
hp.Messages[1].Signature = sig.Sign(signing.HARE, hp.Messages[1].SignedBytes())
hp.Messages[1].SmesherID = sig.NodeID()
gossip := &types.MalfeasanceGossip{
MalfeasanceProof: types.MalfeasanceProof{
Layer: lid,
Expand All @@ -607,7 +640,7 @@ func TestHandler_HandleMalfeasanceProof_hareEquivocation(t *testing.T) {
}
data, err := codec.Encode(gossip)
require.NoError(t, err)
require.Error(t, h.HandleMalfeasanceProof(context.Background(), "peer", data))
require.ErrorIs(t, h.HandleMalfeasanceProof(context.Background(), "peer", data), pubsub.ErrValidationReject)

malProof, err := identities.GetMalfeasanceProof(db, sig.NodeID())
require.ErrorIs(t, err, sql.ErrNotFound)
Expand All @@ -631,14 +664,14 @@ func TestHandler_HandleMalfeasanceProof_hareEquivocation(t *testing.T) {
}
data, err := codec.Encode(gossip)
require.NoError(t, err)
require.Error(t, h.HandleMalfeasanceProof(context.Background(), "peer", data))
require.ErrorIs(t, h.HandleMalfeasanceProof(context.Background(), "peer", data), pubsub.ErrValidationReject)

malProof, err := identities.GetMalfeasanceProof(db, sig.NodeID())
require.ErrorIs(t, err, sql.ErrNotFound)
require.Nil(t, malProof)
})

t.Run("invalid hare eligibility", func(t *testing.T) {
t.Run("hare eligibility is deprecated", func(t *testing.T) {
hp := hareProof
hp.Messages[0].Signature = sig.Sign(signing.HARE, hp.Messages[0].SignedBytes())
hp.Messages[1].Signature = sig.Sign(signing.HARE, hp.Messages[1].SignedBytes())
Expand All @@ -656,7 +689,7 @@ func TestHandler_HandleMalfeasanceProof_hareEquivocation(t *testing.T) {
}
data, err := codec.Encode(gossip)
require.NoError(t, err)
require.Error(t, h.HandleMalfeasanceProof(context.Background(), "peer", data))
require.ErrorIs(t, h.HandleMalfeasanceProof(context.Background(), "peer", data), pubsub.ErrValidationReject)
})

t.Run("valid", func(t *testing.T) {
Expand Down Expand Up @@ -716,68 +749,6 @@ func TestHandler_HandleMalfeasanceProof_hareEquivocation(t *testing.T) {
})
}

func TestHandler_HandleMalfeasanceProof_validateHare(t *testing.T) {
db := sql.InMemory()
lg := logtest.New(t)
ctrl := gomock.NewController(t)
trt := malfeasance.NewMocktortoise(ctrl)
postVerifier := malfeasance.NewMockpostVerifier(ctrl)

h := malfeasance.NewHandler(
datastore.NewCachedDB(db, lg),
lg,
"self",
[]types.NodeID{types.RandomNodeID()},
signing.NewEdVerifier(),
trt,
postVerifier,
)
sig, err := signing.NewEdSigner()
require.NoError(t, err)
createIdentity(t, db, sig)
lid := types.LayerID(11)

bp := types.BallotProof{
Messages: [2]types.BallotProofMsg{
{
InnerMsg: types.BallotMetadata{
Layer: lid,
MsgHash: types.RandomHash(),
},
},
{
InnerMsg: types.BallotMetadata{
Layer: lid,
MsgHash: types.RandomHash(),
},
},
},
}
bp.Messages[0].Signature = sig.Sign(signing.BALLOT, bp.Messages[0].SignedBytes())
bp.Messages[0].SmesherID = sig.NodeID()
bp.Messages[1].Signature = sig.Sign(signing.BALLOT, bp.Messages[1].SignedBytes())
bp.Messages[1].SmesherID = sig.NodeID()
gossip := &types.MalfeasanceGossip{
MalfeasanceProof: types.MalfeasanceProof{
Layer: lid,
Proof: types.Proof{
Type: types.MultipleBallots,
Data: &bp,
},
},
}

t.Run("different node id", func(t *testing.T) {
gs := gossip
gs.Eligibility = &types.HareEligibilityGossip{
NodeID: types.RandomNodeID(),
}
data, err := codec.Encode(gs)
require.NoError(t, err)
require.Error(t, h.HandleMalfeasanceProof(context.Background(), "peer", data))
})
}

func TestHandler_CrossDomain(t *testing.T) {
db := sql.InMemory()
lg := logtest.New(t)
Expand Down Expand Up @@ -1182,7 +1153,7 @@ func TestHandler_HandleMalfeasanceProof_InvalidPostIndex(t *testing.T) {

postVerifier.EXPECT().Verify(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
err := h.HandleSyncedMalfeasanceProof(context.Background(), nodeIdH32, "peer", codec.MustEncode(&proof))
require.Error(t, err)
require.ErrorIs(t, err, pubsub.ErrValidationReject)

malicious, err := identities.IsMalicious(db, sig.NodeID())
require.NoError(t, err)
Expand Down Expand Up @@ -1220,6 +1191,7 @@ func TestHandler_HandleMalfeasanceProof_InvalidPostIndex(t *testing.T) {
}

err := h.HandleSyncedMalfeasanceProof(context.Background(), nodeIdH32, "peer", codec.MustEncode(&proof))
require.ErrorIs(t, err, pubsub.ErrValidationReject)
require.ErrorContains(t, err, "invalid signature")

malicious, err := identities.IsMalicious(db, sig.NodeID())
Expand Down

0 comments on commit 455f0de

Please sign in to comment.